#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007  Donald N. Allingham
# Copyright (C) 2009-2011  Gary Burton
# Copyright (C) 2010       Nick Hall
# Copyright (C) 2011       Tim G L Lyons
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

"""
EditPerson Dialog. Provide the interface to allow the GRAMPS program
to edit information about a particular Person.
"""

#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
from copy import copy
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext

#-------------------------------------------------------------------------
#
# GTK/Gnome modules
#
#-------------------------------------------------------------------------
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Pango

#-------------------------------------------------------------------------
#
# gramps modules
#
#-------------------------------------------------------------------------
from gramps.gen.utils.file import media_path_full
from ..thumbnails import get_thumbnail_image
from ..utils import is_right_click, open_file_with_default_application
from gramps.gen.utils.db import get_birth_or_fallback
from gramps.gen.lib import NoteType, Person, Surname
from gramps.gen.db import DbTxn
from .. import widgets
from gramps.gen.display.name import displayer as name_displayer
from gramps.gen.errors import WindowActiveError
from ..glade import Glade
from ..widgets.menuitem import add_menuitem

from .editprimary import EditPrimary
from .editmediaref import EditMediaRef
from .editname import EditName
from gramps.gen.config import config
from ..dialog import ErrorDialog, ICON
from gramps.gen.errors import ValidationError

from .displaytabs import (PersonEventEmbedList, NameEmbedList, CitationEmbedList,
                         AttrEmbedList, AddrEmbedList, NoteTab, GalleryTab,
                         WebEmbedList, PersonRefEmbedList, LdsEmbedList,
                         PersonBackRefList, SurnameTab)
from gramps.gen.plug import CATEGORY_QR_PERSON
from gramps.gen.const import URL_MANUAL_PAGE
from gramps.gen.constfunc import cuni

#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------

WIKI_HELP_PAGE = _('%s_-_Entering_and_editing_data:_detailed_-_part_1') % URL_MANUAL_PAGE

_select_gender = ((True, False, False),
                  (False, True, False),
                  (False, False, True))

class SingSurn(object):
    """
    Managing the single surname components
    """
    def __init__(self, gladeobj):
        self.top = gladeobj

    def hide_all(self):
        #self.top.get_object('prefixlabel').hide()
        self.top.get_object('prefix').hide()
        self.top.get_object('surnamelabel').hide()
        self.top.get_object('surname').hide()
        self.top.get_object('originlabel').hide()
        self.top.get_object('originlabel').hide()
        self.top.get_object('cmborigin').hide()
        self.top.get_object('multsurnamebtn').hide()

    def show_all(self):
        #self.top.get_object('prefixlabel').show()
        self.top.get_object('prefix').show()
        self.top.get_object('surnamelabel').show()
        self.top.get_object('surname').show()
        self.top.get_object('originlabel').show()
        self.top.get_object('originlabel').show()
        self.top.get_object('cmborigin').show()
        self.top.get_object('multsurnamebtn').show()

class EditPerson(EditPrimary):
    """
    The EditPerson dialog is derived from the EditPrimary class.

    It allows for the editing of the primary object type of Person.

    """

    QR_CATEGORY = CATEGORY_QR_PERSON

    def __init__(self, dbstate, uistate, track, person, callback=None):
        """
        Create an EditPerson window.

        Associate a person with the window.

        """
        EditPrimary.__init__(self, dbstate, uistate, track, person,
                             dbstate.db.get_person_from_handle,
                             dbstate.db.get_person_from_gramps_id, callback)

    def empty_object(self):
        """
        Return an empty Person object for comparison for changes.

        This is used by the base class (EditPrimary).

        """
        person = Person()
        #the editor requires a surname
        person.primary_name.add_surname(Surname())
        person.primary_name.set_primary_surname(0)
        return person

    def get_menu_title(self):
        if self.obj and self.obj.get_handle():
            name = name_displayer.display(self.obj)
            title = _('Person: %(name)s') % {'name': name}
        else:
            name = name_displayer.display(self.obj)
            if name:
                title = _('New Person: %(name)s')  % {'name': name}
            else:
                title = _('New Person')
        return title

    def get_preview_name(self):
        prevname = name_displayer.display(self.obj)
        return prevname

    def _local_init(self):
        """
        Performs basic initialization, including setting up widgets and the
        glade interface.

        Local initialization function.
        This is called by the base class of EditPrimary, and overridden here.

        """
        self.width_key = 'interface.person-width'
        self.height_key = 'interface.person-height'
        self.pname = self.obj.get_primary_name()
        self.should_guess_gender = (not self.obj.get_gramps_id() and
                                    self.obj.get_gender () ==
                                    Person.UNKNOWN)

        self.top = Glade()

        self.set_window(self.top.toplevel, None,
                        self.get_menu_title())

        self.obj_photo = self.top.get_object("personPix")
        self.frame_photo = self.top.get_object("frame5")
        self.eventbox = self.top.get_object("eventbox1")
        self.singsurnfr = SingSurn(self.top)
        self.multsurnfr = self.top.get_object("hboxmultsurnames")
        self.singlesurn_active = True
        self.surntab = SurnameTab(self.dbstate, self.uistate, self.track,
                                  self.obj.get_primary_name(),
                                  on_change=self._changed_name)

        self.set_contexteventbox(self.top.get_object("eventboxtop"))

    def _post_init(self):
        """
        Handle any initialization that needs to be done after the interface is
        brought up.

        Post initalization function.
        This is called by _EditPrimary's init routine, and overridden in the
        derived class (this class).

        """
        self.load_person_image()
        self.given.grab_focus()
        self._changed_name(None)
        self.top.get_object("hboxmultsurnames").pack_start(self.surntab, True, True, 0)

        if len(self.obj.get_primary_name().get_surname_list()) > 1:
            self.multsurnfr.set_size_request(-1,
                                int(config.get('interface.surname-box-height')))
            self.singsurnfr.hide_all()
            self.multsurnfr.show_all()
            self.singlesurn_active = False
        else:
            self.multsurnfr.hide()
            self.singsurnfr.show_all()
            self.singlesurn_active = True
        #if self.pname.get_surname() and not self.pname.get_first_name():
        #    self.given.grab_focus()
        #else:
        #    self.surname_field.grab_focus()

    def _connect_signals(self):
        """
        Connect any signals that need to be connected.
        Called by the init routine of the base class (_EditPrimary).
        """
        self.define_cancel_button(self.top.get_object("button15"))
        self.define_ok_button(self.top.get_object("ok"), self.save)
        self.define_help_button(self.top.get_object("button134"),
                WIKI_HELP_PAGE,
                _('manpage section id|Editing_information_about_people'))

        self.given.connect("focus_out_event", self._given_focus_out_event)
        self.top.get_object("editnamebtn").connect("clicked",
                                                 self._edit_name_clicked)
        self.top.get_object("multsurnamebtn").connect("clicked",
                                                 self._mult_surn_clicked)

        self.eventbox.connect('button-press-event',
                                self._image_button_press)

    def _connect_db_signals(self):
        """
        Connect any signals that need to be connected.
        Called by the init routine of the base class (_EditPrimary).
        """
        self._add_db_signal('person-rebuild', self._do_close)
        self._add_db_signal('person-delete', self.check_for_close)
        self._add_db_signal('family-rebuild', self.family_change)
        self._add_db_signal('family-delete', self.family_change)
        self._add_db_signal('family-update', self.family_change)
        self._add_db_signal('family-add', self.family_change)
        self._add_db_signal('event-update', self.event_updated)
        self._add_db_signal('event-rebuild', self.event_updated)
        self._add_db_signal('event-delete', self.event_updated)

    def family_change(self, handle_list=[]):
        """
        Callback for family change signals.

        This should rebuild the
           backreferences to family in person when:
            1)a family the person is parent of changes. Person could have
              been removed
            2)a family the person is child in changes. Child could have been
              removed
            3)a family is changed. The person could be added as child or
              parent

        """
        #As this would be an extensive check, we choose the easy path and
        #   rebuild family backreferences on all family changes
        self._update_families()

    def _update_families(self):
        phandle = self.obj.get_handle()
        if phandle:
            #new person has no handle yet and cannot be in a family.
            person = self.dbstate.db.get_person_from_handle(phandle)
            self.obj.set_family_handle_list(person.get_family_handle_list())
            self.obj.set_parent_family_handle_list(
                                        person.get_parent_family_handle_list())
            #a family groupname in event_list might need to be changed
            # we just rebuild the view always
            self.event_list.rebuild_callback()

    def event_updated(self, obj):
        #place in event might have changed, or person event shown in the list
        # we just rebuild the view always
        self.event_list.rebuild_callback()

    def _validate_call(self, widget, text):
        """ a callname must be a part of the given name, see if this is the
            case """
        validcall = self.given.obj.get_text().split()
        dummy = copy(validcall)
        for item in dummy:
            validcall += item.split('-')
        if text in validcall:
            return
        return ValidationError(_("Call name must be the given name that "
                                     "is normally used."))

    def _setup_fields(self):
        """
        Connect the GrampsWidget objects to field in the interface.

        This allows the widgets to keep the data in the attached Person object
        up to date at all times, eliminating a lot of need in 'save' routine.

        """

        self.private = widgets.PrivacyButton(
            self.top.get_object('private'),
            self.obj,
            self.db.readonly)

        self.gender = widgets.MonitoredMenu(
            self.top.get_object('gender'),
            self.obj.set_gender,
            self.obj.get_gender,
            (
            (_('female'), Person.FEMALE),
            (_('male'), Person.MALE),
            (_('unknown'), Person.UNKNOWN)
            ),
            self.db.readonly)

        self.ntype_field = widgets.MonitoredDataType(
            self.top.get_object("ntype"),
            self.pname.set_type,
            self.pname.get_type,
            self.db.readonly,
            self.db.get_name_types())

        #part of Given Name section
        self.given = widgets.MonitoredEntry(
            self.top.get_object("given_name"),
            self.pname.set_first_name,
            self.pname.get_first_name,
            self.db.readonly)

        self.call = widgets.MonitoredEntry(
            self.top.get_object("call"),
            self.pname.set_call_name,
            self.pname.get_call_name,
            self.db.readonly)
        self.call.connect("validate", self._validate_call)
        #force validation now with initial entry
        self.call.obj.validate(force=True)

        self.title = widgets.MonitoredEntry(
            self.top.get_object("title"),
            self.pname.set_title,
            self.pname.get_title,
            self.db.readonly)

        self.suffix = widgets.MonitoredEntryIndicator(
            self.top.get_object("suffix"),
            self.pname.set_suffix,
            self.pname.get_suffix,
            _('suffix'),
            self.db.readonly)

        self.nick = widgets.MonitoredEntry(
            self.top.get_object("nickname"),
            self.pname.set_nick_name,
            self.pname.get_nick_name,
            self.db.readonly)

        #part of Single Surname section
        self.surname_field = widgets.MonitoredEntry(
            self.top.get_object("surname"),
            self.pname.get_primary_surname().set_surname,
            self.pname.get_primary_surname().get_surname,
            self.db.readonly,
            autolist=self.db.get_surname_list() if not self.db.readonly else [])

        self.prefix = widgets.MonitoredEntryIndicator(
            self.top.get_object("prefix"),
            self.pname.get_primary_surname().set_prefix,
            self.pname.get_primary_surname().get_prefix,
            _('prefix'),
            self.db.readonly)

        self.ortype_field = widgets.MonitoredDataType(
            self.top.get_object("cmborigin"),
            self.pname.get_primary_surname().set_origintype,
            self.pname.get_primary_surname().get_origintype,
            self.db.readonly,
            self.db.get_origin_types())

        #other fields

        self.tags = widgets.MonitoredTagList(
            self.top.get_object("tag_label"),
            self.top.get_object("tag_button"),
            self.obj.set_tag_list,
            self.obj.get_tag_list,
            self.db,
            self.uistate, self.track,
            self.db.readonly)

        self.gid = widgets.MonitoredEntry(
            self.top.get_object("gid"),
            self.obj.set_gramps_id,
            self.obj.get_gramps_id,
            self.db.readonly)

        #make sure title updates automatically
        for obj in [self.top.get_object("given_name"),
                    self.top.get_object("call"),
                    self.top.get_object("suffix"),
                    self.top.get_object("prefix"),
                    self.top.get_object("surname"),
                    ]:
            obj.connect('changed', self._changed_name)

        self.preview_name = self.top.get_object("full_name")
        self.preview_name.modify_font(Pango.FontDescription('sans bold 12'))

    def get_start_date(self):
        """
        Get the start date for a person, usually a birth date, or
        something close to birth.
        """
        event = get_birth_or_fallback(self.dbstate.db, self.obj)
        return event.get_date_object() if event else None

    def _create_tabbed_pages(self):
        """
        Create the notebook tabs and insert them into the main window.
        """
        notebook = Gtk.Notebook()
        notebook.set_scrollable(True)

        self.event_list = PersonEventEmbedList(
            self.dbstate,
            self.uistate,
            self.track,
            self.obj,
            start_date=self.get_start_date())

        self._add_tab(notebook, self.event_list)
        self.track_ref_for_deletion("event_list")

        self.name_list = NameEmbedList(self.dbstate,
                                       self.uistate,
                                       self.track,
                                       self.obj.get_alternate_names(),
                                       self.obj,
                                       self.name_callback)
        self._add_tab(notebook, self.name_list)
        self.track_ref_for_deletion("name_list")

        self.srcref_list = CitationEmbedList(self.dbstate,
                                           self.uistate,
                                           self.track,
                                           self.obj.get_citation_list(),
                                           self.get_menu_title())
        self._add_tab(notebook, self.srcref_list)
        self.track_ref_for_deletion("srcref_list")

        self.attr_list = AttrEmbedList(self.dbstate,
                                       self.uistate,
                                       self.track,
                                       self.obj.get_attribute_list())
        self._add_tab(notebook, self.attr_list)
        self.track_ref_for_deletion("attr_list")

        self.addr_list = AddrEmbedList(self.dbstate,
                                       self.uistate,
                                       self.track,
                                       self.obj.get_address_list())
        self._add_tab(notebook, self.addr_list)
        self.track_ref_for_deletion("addr_list")

        self.note_tab = NoteTab(self.dbstate,
                                self.uistate,
                                self.track,
                                self.obj.get_note_list(),
                                self.get_menu_title(),
                                notetype=NoteType.PERSON)
        self._add_tab(notebook, self.note_tab)
        self.track_ref_for_deletion("note_tab")

        self.gallery_tab = GalleryTab(self.dbstate,
                                      self.uistate,
                                      self.track,
                                      self.obj.get_media_list(),
                                      self.load_person_image)
        self._add_tab(notebook, self.gallery_tab)
        self.track_ref_for_deletion("gallery_tab")

        self.web_list = WebEmbedList(self.dbstate,
                                     self.uistate,
                                     self.track,
                                     self.obj.get_url_list())
        self._add_tab(notebook, self.web_list)
        self.track_ref_for_deletion("web_list")

        self.person_ref_list = PersonRefEmbedList(self.dbstate, self.uistate,
                                                  self.track,
                                                  self.obj.get_person_ref_list())
        self._add_tab(notebook, self.person_ref_list)
        self.track_ref_for_deletion("person_ref_list")

        self.lds_list = LdsEmbedList(self.dbstate,
                                     self.uistate,
                                     self.track,
                                     self.obj.get_lds_ord_list())
        self._add_tab(notebook, self.lds_list)
        self.track_ref_for_deletion("lds_list")

        self.backref_tab = PersonBackRefList(self.dbstate,
                                             self.uistate,
                                             self.track,
                              self.db.find_backlink_handles(self.obj.handle))
        self._add_tab(notebook, self.backref_tab)
        self.track_ref_for_deletion("backref_tab")

        self._setup_notebook_tabs(notebook)
        notebook.show_all()
        self.top.get_object('vbox').pack_start(notebook, True, True, 0)

    def _changed_name(self, *obj):
        """
        callback to changes typed by user to the person name.
        Update the window title, and default name in name tab
        """
        self.update_title(self.get_menu_title())
        self.preview_name.set_text(self.get_preview_name())
        self.name_list.update_defname()

    def name_callback(self):
        """
        Callback if changes happen in the name tab that impact the preferred
        name.
        """
        self.pname = self.obj.get_primary_name()

        self.ntype_field.reinit(self.pname.set_type, self.pname.get_type)
        self.given.reinit(self.pname.set_first_name, self.pname.get_first_name)
        self.call.reinit(self.pname.set_call_name, self.pname.get_call_name)
        self.title.reinit(self.pname.set_title, self.pname.get_title)
        self.suffix.reinit(self.pname.set_suffix, self.pname.get_suffix)
        self.nick.reinit(self.pname.set_nick_name, self.pname.get_nick_name)
        #part of Single Surname section
        self.surname_field.reinit(
            self.pname.get_primary_surname().set_surname,
            self.pname.get_primary_surname().get_surname)

        self.prefix.reinit(
            self.pname.get_primary_surname().set_prefix,
            self.pname.get_primary_surname().get_prefix)

        self.ortype_field.reinit(
            self.pname.get_primary_surname().set_origintype,
            self.pname.get_primary_surname().get_origintype)

        self.__renewmultsurnames()

        if len(self.pname.get_surname_list()) == 1:
            self.singlesurn_active = True
        else:
            self.singlesurn_active = False
        if self.singlesurn_active:
            self.multsurnfr.hide()
            self.singsurnfr.show_all()
        else:
            self.singsurnfr.hide_all()
            self.multsurnfr.show_all()

    def build_menu_names(self, person):
        """
        Provide the information needed by the base class to define the
        window management menu entries.
        """
        return (_('Edit Person'), self.get_menu_title())

    def _image_button_press(self, obj, event):
        """
        Button press event that is caught when a button has been pressed while
        on the image on the main form.

        This does not apply to the images in galleries, just the image on the
        main form.

        """
        if event.type == Gdk.EventType._2BUTTON_PRESS and event.button == 1:

            media_list = self.obj.get_media_list()
            if media_list:
                media_ref = media_list[0]
                object_handle = media_ref.get_reference_handle()
                media_obj = self.db.get_object_from_handle(object_handle)

                try:
                    EditMediaRef(self.dbstate, self.uistate, self.track,
                                 media_obj, media_ref, self.load_photo)
                except WindowActiveError:
                    pass

        elif is_right_click(event):
            media_list = self.obj.get_media_list()
            if media_list:
                photo = media_list[0]
                self._show_popup(photo, event)
        #do not propagate further:
        return True

    def _show_popup(self, photo, event):
        """
        Look for right-clicks on a picture and create a popup menu of the
        available actions.
        """
        self.imgmenu = Gtk.Menu()
        menu = self.imgmenu
        menu.set_title(_("Media Object"))
        obj = self.db.get_object_from_handle(photo.get_reference_handle())
        if obj:
            add_menuitem(menu, _("View"), photo,
                                   self._popup_view_photo)
        add_menuitem(menu, _("Edit Object Properties"), photo,
                               self._popup_change_description)
        menu.popup(None, None, None, None, event.button, event.time)

    def _popup_view_photo(self, obj):
        """
        Open this picture in the default picture viewer.
        """
        media_list = self.obj.get_media_list()
        if media_list:
            photo = media_list[0]
            object_handle = photo.get_reference_handle()
            ref_obj = self.db.get_object_from_handle(object_handle)
            photo_path = media_path_full(self.db, ref_obj.get_path())
            open_file_with_default_application(photo_path)

    def _popup_change_description(self, obj):
        """
        Bring up the EditMediaRef dialog for the image on the main form.
        """
        media_list = self.obj.get_media_list()
        if media_list:
            media_ref = media_list[0]
            object_handle = media_ref.get_reference_handle()
            media_obj = self.db.get_object_from_handle(object_handle)
            EditMediaRef(self.dbstate, self.uistate, self.track,
                         media_obj, media_ref, self.load_photo)

    def _top_contextmenu(self):
        """
        Override from base class, the menuitems and actiongroups for the top
        of context menu.
        """
        self.all_action    = Gtk.ActionGroup(name="/PersonAll")
        self.home_action   = Gtk.ActionGroup(name="/PersonHome")
        self.track_ref_for_deletion("all_action")
        self.track_ref_for_deletion("home_action")

        self.all_action.add_actions([
                ('ActivePerson', Gtk.STOCK_APPLY, _("Make Active Person"),
                    None, None, self._make_active),
                ])
        self.home_action.add_actions([
                ('HomePerson', Gtk.STOCK_HOME, _("Make Home Person"),
                    None, None, self._make_home_person),
                ])

        self.all_action.set_visible(True)
        self.home_action.set_visible(True)

        ui_top_cm = '''
            <menuitem action="ActivePerson"/>
            <menuitem action="HomePerson"/>'''

        return ui_top_cm, [self.all_action, self.home_action]

    def _post_build_popup_ui(self):
        """
        Override base class, make inactive home action if not needed.
        """
        if (self.dbstate.db.get_default_person() and
                self.obj.get_handle() ==
                    self.dbstate.db.get_default_person().get_handle()):
            self.home_action.set_sensitive(False)
        else:
            self.home_action.set_sensitive(True)

    def _make_active(self, obj):
        self.uistate.set_active(self.obj.get_handle(), 'Person')

    def _make_home_person(self, obj):
        handle = self.obj.get_handle()
        if handle:
            self.dbstate.db.set_default_person_handle(handle)

    def _given_focus_out_event (self, entry, event):
        """
        Callback that occurs when the user leaves the given name field,
        allowing us to attempt to guess the gender of the person if
        so requested.
        """
        if not self.should_guess_gender:
            return False
        try:
            gender_type = self.db.genderStats.guess_gender(
                                                    cuni(entry.get_text()))
            self.gender.force(gender_type)
        except:
            return False
        return False

    def _check_for_unknown_gender(self):
        if self.obj.get_gender() == Person.UNKNOWN:
            d = GenderDialog(self.window)
            gender = d.run()
            d.destroy()
            if gender >= 0:
                self.obj.set_gender(gender)

    def _update_family_ids(self):
        # Update each of the families child lists to reflect any
        # change in ordering due to the new birth date
        family = self.obj.get_main_parents_family_handle()
        if (family):
            f = self.db.get_family_from_handle(family)
            new_order = self.reorder_child_ref_list(self.obj,
                                                f.get_child_ref_list())
            f.set_child_ref_list(new_order)
        for family in self.obj.get_parent_family_handle_list():
            f = self.db.get_family_from_handle(family)
            new_order = self.reorder_child_ref_list(self.obj,
                                                f.get_child_ref_list())
            f.set_child_ref_list(new_order)

        error = False
        original = self.db.get_person_from_handle(self.obj.handle)

        if original:
            (female, male, unknown) = _select_gender[self.obj.get_gender()]
            if male and original.get_gender() != Person.MALE:
                for tmp_handle in self.obj.get_family_handle_list():
                    temp_family = self.db.get_family_from_handle(tmp_handle)
                    if self.obj == temp_family.get_mother_handle():
                        if temp_family.get_father_handle() is not None:
                            error = True
                        else:
                            temp_family.set_mother_handle(None)
                            temp_family.set_father_handle(self.obj)
            elif female and original != Person.FEMALE:
                for tmp_handle in self.obj.get_family_handle_list():
                    temp_family = self.db.get_family_from_handle(tmp_handle)
                    if self.obj == temp_family.get_father_handle():
                        if temp_family.get_mother_handle() is not None:
                            error = True
                        else:
                            temp_family.set_father_handle(None)
                            temp_family.set_mother_handle(self.obj)
            elif unknown and original.get_gender() != Person.UNKNOWN:
                for tmp_handle in self.obj.get_family_handle_list():
                    temp_family = self.db.get_family_from_handle(tmp_handle)
                    if self.obj == temp_family.get_father_handle():
                        if temp_family.get_mother_handle() is not None:
                            error = True
                        else:
                            temp_family.set_father_handle(None)
                            temp_family.set_mother_handle(self.obj)
                    if self.obj == temp_family.get_mother_handle():
                        if temp_family.get_father_handle() is not None:
                            error = True
                        else:
                            temp_family.set_mother_handle(None)
                            temp_family.set_father_handle(self.obj)

            if error:
                msg2 = _("Problem changing the gender")
                msg = _("Changing the gender caused problems "
                        "with marriage information.\nPlease check "
                        "the person's marriages.")
                ErrorDialog(msg2, msg)

    def save(self, *obj):
        """
        Save the data.
        """
        self.ok_button.set_sensitive(False)
        if self.object_is_empty():
            ErrorDialog(_("Cannot save person"),
                        _("No data exists for this person. Please "
                          "enter data or cancel the edit."))
            self.ok_button.set_sensitive(True)
            return
        # fix surname problems
        for name in [self.obj.get_primary_name()] + self.obj.get_alternate_names():
            if len(name.get_surname_list()) > 1:
                newlist = [surn for surn in name.get_surname_list() if not surn.is_empty()]
                if len(newlist) != len(name.get_surname_list()):
                    name.set_surname_list(newlist)
            if len(name.get_surname_list()) == 0:
                name.set_surname_list([Surname()])
            try:
                primind = [surn.get_primary() for surn in name.get_surname_list()].index(True)
            except ValueError:
                primind = 0 # no match
            name.set_primary_surname(primind)

        # fix ide problems
        (uses_dupe_id, id) = self._uses_duplicate_id()
        if uses_dupe_id:
            prim_object = self.get_from_gramps_id(id)
            name = self.name_displayer.display(prim_object)
            msg1 = _("Cannot save person. ID already exists.")
            msg2 = _("You have attempted to use the existing Gramps ID with "
                     "value %(id)s. This value is already used by '"
                     "%(prim_object)s'. Please enter a different ID or leave "
                     "blank to get the next available ID value.") % {
                         'id' : id, 'prim_object' : name }
            ErrorDialog(msg1, msg2)
            self.ok_button.set_sensitive(True)
            return

        self._check_for_unknown_gender()

        self.db.set_birth_death_index(self.obj)

        with DbTxn('', self.db) as trans:
            self._update_family_ids()
            if not self.obj.get_handle():
                self.db.add_person(self.obj, trans)
                msg = _("Add Person (%s)") % \
                        self.name_displayer.display(self.obj)
            else:
                if not self.obj.get_gramps_id():
                    self.obj.set_gramps_id(self.db.find_next_person_gramps_id())
                self.db.commit_person(self.obj, trans)
                msg = _("Edit Person (%s)") % \
                        self.name_displayer.display(self.obj)
            trans.set_description(msg)

        self.close()
        if self.callback:
            self.callback(self.obj)
        self.callback = None

    def _edit_name_clicked(self, obj):
        """
        Bring up the EditName dialog for this name.

        Called when the edit name button is clicked for the primary name
        on the main form (not in the names tab).

        """
        try:
            EditName(self.dbstate, self.uistate, self.track,
                 self.pname, self._update_name)
        except WindowActiveError:
            pass

    def _mult_surn_clicked(self, obj):
        """
        Show the list entry of multiple surnames
        """
        self.singsurnfr.hide_all()
        self.singlesurn_active = False
        #update multsurnfr for possible changes
        self.__renewmultsurnames()
        self.multsurnfr.show_all()

    def _update_name(self, name):
        """
        Called when the primary name has been changed by the EditName
        dialog.

        This allows us to update the main form in response to any changes.

        """
        for obj in (self.ntype_field, self.given, self.call, self.title,
                    self.suffix, self.nick, self.surname_field, self.prefix,
                    self.ortype_field):
            obj.update()

        self.__renewmultsurnames()

        if len(self.obj.get_primary_name().get_surname_list()) == 1:
            self.singlesurn_active = True
        else:
            self.singlesurn_active = False
        if self.singlesurn_active:
            self.multsurnfr.hide()
            self.singsurnfr.show_all()

        else:
            self.singsurnfr.hide_all()
            self.multsurnfr.show_all()

    def __renewmultsurnames(self):
        """Update mult surnames section with what is presently the
           correct surname.
           It is easier to recreate the entire mult surnames GUI than
           changing what has changed visually.
        """
        #remove present surname tab, and put new one
        msurhbox = self.top.get_object("hboxmultsurnames")
        msurhbox.remove(self.surntab)
        self.surntab = SurnameTab(self.dbstate, self.uistate, self.track,
                       self.obj.get_primary_name())
        self.multsurnfr.set_size_request(-1,
                                int(config.get('interface.surname-box-height')))
        msurhbox.pack_start(self.surntab, True, True, 0)

    def load_person_image(self):
        """
        Load the primary image into the main form if it exists.

        Used as callback on Gallery Tab too.

        """
        media_list = self.obj.get_media_list()
        if media_list:
            ref = media_list[0]
            handle = ref.get_reference_handle()
            obj = self.dbstate.db.get_object_from_handle(handle)
            if obj is None :
                #notify user of error
                from ..dialog import RunDatabaseRepair
                RunDatabaseRepair(
                            _('Non existing media found in the Gallery'))
            else :
                self.load_photo(ref, obj)
        else:
            self.obj_photo.hide()
            self.frame_photo.hide()

    def load_photo(self, ref, obj):
        """
        Load the person's main photo using the Thumbnailer.
        """
        pixbuf = get_thumbnail_image(
                        media_path_full(self.dbstate.db,
                                              obj.get_path()),
                        obj.get_mime_type(),
                        ref.get_rectangle())

        self.obj_photo.set_from_pixbuf(pixbuf)
        self.obj_photo.show()
        self.frame_photo.show_all()

    def birth_dates_in_order(self, child_ref_list):
        """
        Check any *valid* birthdates in the list to insure that they are in
        numerically increasing order.
        """
        inorder = True
        prev_date = 0
        handle_list = [ref.ref for ref in child_ref_list]
        for i in range(len(handle_list)):


from Tools.Profile import profile
profile("LOAD:ElementTree")
import xml.etree.cElementTree
import os

profile("LOAD:enigma_skin")
from enigma import eSize, ePoint, eRect, gFont, eWindow, eLabel, ePixmap, eWindowStyleManager, \
	addFont, gRGB, eWindowStyleSkinned, getDesktop
from Components.config import ConfigSubsection, ConfigText, config
from Components.Converter.Converter import Converter
from Components.Sources.Source import Source, ObsoleteSource
from Tools.Directories import resolveFilename, SCOPE_SKIN, SCOPE_SKIN_IMAGE, SCOPE_FONTS, SCOPE_CURRENT_SKIN, SCOPE_CONFIG, fileExists
from Tools.Import import my_import
from Tools.LoadPixmap import LoadPixmap
from Components.RcModel import rc_model

colorNames = {}
# Predefined fonts, typically used in built-in screens and for components like
# the movie list and so.
fonts = {
	"Body": ("Regular", 18, 22, 16),
	"ChoiceList": ("Regular", 20, 24, 18),
}

def dump(x, i=0):
	print " " * i + str(x)
	try:
		for n in x.childNodes:
			dump(n, i + 1)
	except:
		None

class SkinError(Exception):
	def __init__(self, message):
		self.msg = message

	def __str__(self):
		return "{%s}: %s. Please contact the skin's author!" % (config.skin.primary_skin.value, self.msg)

dom_skins = [ ]

def addSkin(name, scope = SCOPE_SKIN):
	# read the skin
	filename = resolveFilename(scope, name)
	if fileExists(filename):
		mpath = os.path.dirname(filename) + "/"
		dom_skins.append((mpath, xml.etree.cElementTree.parse(filename).getroot()))
		return True
	return False

# get own skin_user_skinname.xml file, if exist
def skin_user_skinname():
	name = "skin_user_" + config.skin.primary_skin.value[:config.skin.primary_skin.value.rfind('/')] + ".xml"
	filename = resolveFilename(SCOPE_CONFIG, name)
	if fileExists(filename):
		return name
	return None

# we do our best to always select the "right" value
# skins are loaded in order of priority: skin with
# highest priority is loaded last, usually the user-provided
# skin.

# currently, loadSingleSkinData (colors, bordersets etc.)
# are applied one-after-each, in order of ascending priority.
# the dom_skin will keep all screens in descending priority,
# so the first screen found will be used.

# example: loadSkin("nemesis_greenline/skin.xml")
config.skin = ConfigSubsection()

# [ IQON : default skin changed force1
#DEFAULT_SKIN = "PLi-HD/skin.xml"
DEFAULT_SKIN = "force1/skin.xml"

# IQON ]

# on SD hardware, PLi-HD will not be available
if not fileExists(resolveFilename(SCOPE_SKIN, DEFAULT_SKIN)):
	# in that case, fallback to Magic (which is an SD skin)
	DEFAULT_SKIN = "Magic/skin.xml"
config.skin.primary_skin = ConfigText(default=DEFAULT_SKIN)

profile("LoadSkin")
try:
	name = skin_user_skinname()
	if name is not None:
		addSkin(name, SCOPE_CONFIG)
	else:
		addSkin('skin_user.xml', SCOPE_CONFIG)
except (SkinError, IOError, AssertionError), err:
	print "not loading user skin: ", err

# some boxes lie about their dimensions
addSkin('skin_box.xml')
# add optional discrete second infobar
addSkin('skin_second_infobar.xml')
# Only one of these is present, compliments of AM_CONDITIONAL
display_skin_id = 1
addSkin('skin_display.xml')
if addSkin('skin_display96.xml'):
	# Color OLED
	display_skin_id = 2
addSkin('skin_text.xml')

addSkin('skin_subtitles.xml')

try:
	if not addSkin(config.skin.primary_skin.value):
		raise SkinError, "primary skin not found"
except Exception, err:
	print "SKIN ERROR:", err
	skin = DEFAULT_SKIN
	if config.skin.primary_skin.value == skin:
		skin = 'skin.xml'
	print "defaulting to standard skin...", skin
	config.skin.primary_skin.value = skin
	addSkin(skin)
	del skin

addSkin('skin_default.xml')
profile("LoadSkinDefaultDone")

def parseCoordinate(s, e, size=0, font=None):
	s = s.strip()
	if s == "center":
		val = (e - size)/2
	elif s == '*':
	        return None
	else:
		if s[0] is 'e':
			val = e
			s = s[1:]
		elif s[0] is 'c':
			val = e/2
			s = s[1:]
		else:
			val = 0;
		if s:
			if s[-1] is '%':
				val += e * int(s[:-1]) / 100
			elif s[-1] is 'w':
			        val += fonts[font][3] * int(s[:-1]);
			elif s[-1] is 'h':
			        val += fonts[font][2] * int(s[:-1]);
			else:
				val += int(s)
	if val < 0:
		val = 0
	return val

def getParentSize(object, desktop):
	size = eSize()
	if object:
		parent = object.getParent()
		# For some widgets (e.g. ScrollLabel) the skin attributes are applied to
		# a child widget, instead of to the widget itself. In that case, the parent
		# we have here is not the real parent, but it is the main widget.
		# We have to go one level higher to get the actual parent.
		# We can detect this because the 'parent' will not have a size yet
		# (the main widget's size will be calculated internally, as soon as the child
		# widget has parsed the skin attributes)
		if parent and parent.size().isEmpty():
			parent = parent.getParent()
		if parent:
			size = parent.size()
		elif desktop:
			#widget has no parent, use desktop size instead for relative coordinates
			size = desktop.size()
	return size

def parsePosition(s, scale, object = None, desktop = None, size = None):
	x, y = s.split(',')
	parentsize = eSize()
	if object and (x[0] in ('c', 'e') or y[0] in ('c', 'e')):
		parentsize = getParentSize(object, desktop)
	xval = parseCoordinate(x, parentsize.width(), size and size.width())
	yval = parseCoordinate(y, parentsize.height(), size and size.height())
	return ePoint(xval * scale[0][0] / scale[0][1], yval * scale[1][0] / scale[1][1])

def parseSize(s, scale, object = None, desktop = None):
	x, y = s.split(',')
	parentsize = eSize()
	if object and (x[0] in ('c', 'e') or y[0] in ('c', 'e')):
		parentsize = getParentSize(object, desktop)
	xval = parseCoordinate(x, parentsize.width())
	yval = parseCoordinate(y, parentsize.height())
	return eSize(xval * scale[0][0] / scale[0][1], yval * scale[1][0] / scale[1][1])

def parseFont(s, scale):
	try:
		f = fonts[s]
		name = f[0]
		size = f[1]
	except:
		name, size = s.split(';')
	return gFont(name, int(size) * scale[0][0] / scale[0][1])

def parseColor(s):
	if s[0] != '#':
		try:
			return colorNames[s]
		except:
			raise SkinError("color '%s' must be #aarrggbb or valid named color" % (s))
	return gRGB(int(s[1:], 0x10))

def collectAttributes(skinAttributes, node, context, skin_path_prefix=None, ignore=(), filenames=frozenset(("pixmap", "pointer", "seek_pointer", "backgroundPixmap", "selectionPixmap", "sliderPixmap", "scrollbarbackgroundPixmap"))):
	# walk all attributes
	size = None
	pos = None
	font = None
	for attrib, value in node.items():
		if attrib not in ignore:
			if attrib in filenames:
				value = resolveFilename(SCOPE_SKIN_IMAGE, value, path_prefix=skin_path_prefix)
			# Bit of a hack this, really. When a window has a flag (e.g. wfNoBorder)
			# it needs to be set at least before the size is set, in order for the
			# window dimensions to be calculated correctly in all situations.
			# If wfNoBorder is applied after the size has been set, the window will fail to clear the title area.
			# Similar situation for a scrollbar in a listbox; when the scrollbar setting is applied after
			# the size, a scrollbar will not be shown until the selection moves for the first time
			if attrib == 'size':
			        size = value.encode("utf-8")
			elif attrib == 'position':
			        pos = value.encode("utf-8")
			elif attrib == 'font':
			        font = value.encode("utf-8")
			        skinAttributes.append((attrib, font))
			else:
				skinAttributes.append((attrib, value.encode("utf-8")))
	if pos is not None:
		pos, size = context.parse(pos, size, font)
		skinAttributes.append(('position', pos))
	if size is not None:
		skinAttributes.append(('size', size))

def morphRcImagePath(value):
	if rc_model.rcIsDefault() is False:
		if value == '/usr/share/enigma2/skin_default/rc.png' or value == '/usr/share/enigma2/skin_default/rcold.png':
			value = rc_model.getRcImg()
	return value

def loadPixmap(path, desktop):
	option = path.find("#")
	if option != -1:
		path = path[:option]
	ptr = LoadPixmap(morphRcImagePath(path), desktop)
	if ptr is None:
		raise SkinError("pixmap file %s not found!" % (path))
	return ptr

class AttributeParser:
	def __init__(self, guiObject, desktop, scale = ((1,1),(1,1))):
		self.guiObject = guiObject
		self.desktop = desktop
		self.scale = scale
	def applyOne(self, attrib, value):
		try:
			getattr(self, attrib)(value)
		except AttributeError:
			print "[Skin] Attribute not implemented:", attrib, "value:", value
		except SkinError, ex:
			print "[Skin] Error:", ex
	def applyAll(self, attrs):
		for attrib, value in attrs:
			try:
				getattr(self, attrib)(value)
			except AttributeError:
				print "[Skin] Attribute not implemented:", attrib, "value:", value
			except SkinError, ex:
				print "[Skin] Error:", ex
	def conditional(self, value):
		pass
	def position(self, value):
		if isinstance(value, tuple):
			self.guiObject.move(ePoint(*value))
		else:
			self.guiObject.move(parsePosition(value, self.scale, self.guiObject, self.desktop, self.guiObject.csize()))
	def size(self, value):
		if isinstance(value, tuple):
			self.guiObject.resize(eSize(*value))
		else:
			self.guiObject.resize(parseSize(value, self.scale, self.guiObject, self.desktop))
	def title(self, value):
		self.guiObject.setTitle(_(value))
	def text(self, value):
		self.guiObject.setText(_(value))
	def font(self, value):
		self.guiObject.setFont(parseFont(value, self.scale))
	def zPosition(self, value):
		self.guiObject.setZPosition(int(value))
	def itemHeight(self, value):
		self.guiObject.setItemHeight(int(value))
	def pixmap(self, value):
		ptr = loadPixmap(value, self.desktop)
		self.guiObject.setPixmap(ptr)
	def backgroundPixmap(self, value):
		ptr = loadPixmap(value, self.desktop)
		self.guiObject.setBackgroundPicture(ptr)
	def selectionPixmap(self, value):
		ptr = loadPixmap(value, self.desktop)
		self.guiObject.setSelectionPicture(ptr)
	def sliderPixmap(self, value):
		ptr = loadPixmap(value, self.desktop)
		self.guiObject.setSliderPicture(ptr)
	def scrollbarbackgroundPixmap(self, value):
		ptr = loadPixmap(value, self.desktop)
		self.guiObject.setScrollbarBackgroundPicture(ptr)
	def alphatest(self, value):
		self.guiObject.setAlphatest(
			{ "on": 1,
			  "off": 0,
			  "blend": 2,
			}[value])
	def scale(self, value):
		self.guiObject.setScale(1)
	def orientation(self, value): # used by eSlider
		try:
			self.guiObject.setOrientation(*
				{ "orVertical": (self.guiObject.orVertical, False),
					"orTopToBottom": (self.guiObject.orVertical, False),
					"orBottomToTop": (self.guiObject.orVertical, True),
					"orHorizontal": (self.guiObject.orHorizontal, False),
					"orLeftToRight": (self.guiObject.orHorizontal, False),
					"orRightToLeft": (self.guiObject.orHorizontal, True),
				}[value])
		except KeyError:
			print "oprientation must be either orVertical or orHorizontal!"
	def valign(self, value):
		try:
			self.guiObject.setVAlign(
				{ "top": self.guiObject.alignTop,
					"center": self.guiObject.alignCenter,
					"bottom": self.guiObject.alignBottom
				}[value])
		except KeyError:
			print "valign must be either top, center or bottom!"
	def halign(self, value):
		try:
			self.guiObject.setHAlign(
				{ "left": self.guiObject.alignLeft,
					"center": self.guiObject.alignCenter,
					"right": self.guiObject.alignRight,
					"block": self.guiObject.alignBlock
				}[value])
		except KeyError:
			print "halign must be either left, center, right or block!"
	def textOffset(self, value):
		x, y = value.split(',')
		self.guiObject.setTextOffset(ePoint(int(x) * self.scale[0][0] / self.scale[0][1], int(y) * self.scale[1][0] / self.scale[1][1]))
	def flags(self, value):
		flags = value.split(',')
		for f in flags:
			try:
				fv = eWindow.__dict__[f]
				self.guiObject.setFlag(fv)
			except KeyError:
				print "illegal flag %s!" % f
	def backgroundColor(self, value):
		self.guiObject.setBackgroundColor(parseColor(value))
	def backgroundColorSelected(self, value):
		self.guiObject.setBackgroundColorSelected(parseColor(value))
	def foregroundColor(self, value):
		self.guiObject.setForegroundColor(parseColor(value))
	def foregroundColorSelected(self, value):
		self.guiObject.setForegroundColorSelected(parseColor(value))
	def shadowColor(self, value):
		self.guiObject.setShadowColor(parseColor(value))
	def selectionDisabled(self, value):
		self.guiObject.setSelectionEnable(0)
	def transparent(self, value):
		self.guiObject.setTransparent(int(value))
	def borderColor(self, value):
		self.guiObject.setBorderColor(parseColor(value))
	def borderWidth(self, value):
		self.guiObject.setBorderWidth(int(value))
	def scrollbarMode(self, value):
		self.guiObject.setScrollbarMode(getattr(self.guiObject, value))
		#	{ "showOnDemand": self.guiObject.showOnDemand,
		#		"showAlways": self.guiObject.showAlways,
		#		"showNever": self.guiObject.showNever,
		#		"showLeft": self.guiObject.showLeft
		#	}[value])
	def enableWrapAround(self, value):
		self.guiObject.setWrapAround(True)
	def itemHeight(self, value):
		self.guiObject.setItemHeight(int(value))
	def pointer(self, value):
		(name, pos) = value.split(':')
		pos = parsePosition(pos, self.scale)
		ptr = loadPixmap(name, self.desktop)
		self.guiObject.setPointer(0, ptr, pos)
	def seek_pointer(self, value):
		(name, pos) = value.split(':')
		pos = parsePosition(pos, self.scale)
		ptr = loadPixmap(name, self.desktop)
		self.guiObject.setPointer(1, ptr, pos)
	def shadowOffset(self, value):
		self.guiObject.setShadowOffset(parsePosition(value, self.scale))
	def noWrap(self, value):
		self.guiObject.setNoWrap(1)

def applySingleAttribute(guiObject, desktop, attrib, value, scale = ((1,1),(1,1))):
	# Someone still using applySingleAttribute?
	AttributeParser(guiObject, desktop, scale).applyOne(attrib, value)

def applyAllAttributes(guiObject, desktop, attributes, scale):
	AttributeParser(guiObject, desktop, scale).applyAll(attributes)

def loadSingleSkinData(desktop, skin, path_prefix):
	"""loads skin data like colors, windowstyle etc."""
	assert skin.tag == "skin", "root element in skin must be 'skin'!"
	for c in skin.findall("output"):
		id = c.attrib.get('id')
		if id:
			id = int(id)
		else:
			id = 0
		if id == 0: # framebuffer
			for res in c.findall("resolution"):
				get_attr = res.attrib.get
				xres = get_attr("xres")
				if xres:
					xres = int(xres)
				else:
					xres = 720
				yres = get_attr("yres")
				if yres:
					yres = int(yres)
				else:
					yres = 576
				bpp = get_attr("bpp")
				if bpp:
					bpp = int(bpp)
				else:
					bpp = 32
				#print "Resolution:", xres,yres,bpp
				from enigma import gMainDC
				gMainDC.getInstance().setResolution(xres, yres)
				desktop.resize(eSize(xres, yres))
				if bpp != 32:
					# load palette (not yet implemented)
					pass

	for skininclude in skin.findall("include"):
		filename = skininclude.attrib.get("filename")
		if filename:
			skinfile = resolveFilename(SCOPE_CURRENT_SKIN, filename, path_prefix=path_prefix)
			if not fileExists(skinfile):
				skinfile = resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix)
			if fileExists(skinfile):
				print "[SKIN] loading include:", skinfile
				loadSkin(skinfile)

	for c in skin.findall("colors"):
		for color in c.findall("color"):
			get_attr = color.attrib.get
			name = get_attr("name")
			color = get_attr("value")
			if name and color:
				colorNames[name] = parseColor(color)
				#print "Color:", name, color
			else:
				raise SkinError("need color and name, got %s %s" % (name, color))

	for c in skin.findall("fonts"):
		for font in c.findall("font"):
			get_attr = font.attrib.get
			filename = get_attr("filename", "<NONAME>")
			name = get_attr("name", "Regular")
			scale = get_attr("scale")
			if scale:
				scale = int(scale)
			else:
				scale = 100
			is_replacement = get_attr("replacement") and True or False
			render = get_attr("render")
			if render:
				render = int(render)
			else:
				render = 0
			resolved_font = resolveFilename(SCOPE_FONTS, filename, path_prefix=path_prefix)
			if not fileExists(resolved_font): #when font is not available look at current skin path
				skin_path = resolveFilename(SCOPE_CURRENT_SKIN, filename)
				if fileExists(skin_path):
					resolved_font = skin_path
			addFont(resolved_font, name, scale, is_replacement, render)
			#print "Font: ", resolved_font, name, scale, is_replacement
		for alias in c.findall("alias"):
		        get = alias.attrib.get
		        try:
				name = get("name")
				font = get("font")
				size = int(get("size"))
				height = int(get("height", size)) # to be calculated some day
				width = int(get("width", size))
				global fonts
				fonts[name] = (font, size, height, width)
			except Exception, ex:
				print "[SKIN] bad font alias", ex

	for c in skin.findall("subtitles"):
		from enigma import eWidget, eSubtitleWidget
		scale = ((1,1),(1,1))
		for substyle in c.findall("sub"):
			get_attr = substyle.attrib.get


#Copyright ReportLab Europe Ltd. 2000-2004
#see license.txt for license details
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/platypus/tableofcontents.py
"""
This module defines a single TableOfContents() class that can be used to
create automatically a table of tontents for Platypus documents like
this:

    story = []
    toc = TableOfContents()
    story.append(toc)
    # some heading paragraphs here...
    doc = MyTemplate(path)
    doc.multiBuild(story)

The data needed to create the table is a list of (level, text, pageNum)
triplets, plus some paragraph styles for each level of the table itself.
The triplets will usually be created in a document template's method
like afterFlowable(), making notification calls using the notify()
method with appropriate data like this:

    (level, text, pageNum) = ...
    self.notify('TOCEntry', (level, text, pageNum))

As the table of contents need at least two passes over the Platypus
story which is why the moultiBuild0() method must be called.

The level<NUMBER>ParaStyle variables are the paragraph styles used
to format the entries in the table of contents. Their indentation
is calculated like this: each entry starts at a multiple of some
constant named delta. If one entry spans more than one line, all
lines after the first are indented by the same constant named
epsilon.
"""
__version__=''' $Id$ '''
import string

from reportlab.lib import enums
from reportlab.lib.units import cm
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.doctemplate import IndexingFlowable
from reportlab.platypus.tables import TableStyle, Table

# Default paragraph styles for tables of contents.
# (This could also be generated automatically or even
# on-demand if it is not known how many levels the
# TOC will finally need to display...)

delta = 1*cm
epsilon = 0.5*cm

levelZeroParaStyle = \
    ParagraphStyle(name='LevelZero',
                   fontName='Times-Roman',
                   fontSize=10,
                   leading=11,
                   firstLineIndent = -epsilon,
                   leftIndent = 0*delta + epsilon)

levelOneParaStyle = \
    ParagraphStyle(name='LevelOne',
                   parent = levelZeroParaStyle,
                   leading=11,
                   firstLineIndent = -epsilon,
                   leftIndent = 1*delta + epsilon)

levelTwoParaStyle = \
    ParagraphStyle(name='LevelTwo',
                   parent = levelOneParaStyle,
                   leading=11,
                   firstLineIndent = -epsilon,
                   leftIndent = 2*delta + epsilon)

levelThreeParaStyle = \
    ParagraphStyle(name='LevelThree',
                   parent = levelTwoParaStyle,
                   leading=11,
                   firstLineIndent = -epsilon,
                   leftIndent = 3*delta + epsilon)

levelFourParaStyle = \
    ParagraphStyle(name='LevelFour',
                   parent = levelTwoParaStyle,
                   leading=11,
                   firstLineIndent = -epsilon,
                   leftIndent = 4*delta + epsilon)

defaultTableStyle = \
    TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP')])

class TableOfContents(IndexingFlowable):
    """This creates a formatted table of contents.

    It presumes a correct block of data is passed in.
    The data block contains a list of (level, text, pageNumber)
    triplets.  You can supply a paragraph style for each level
    (starting at zero).
    """

    def __init__(self):
        self.entries = []
        self.rightColumnWidth = 72
        self.levelStyles = [levelZeroParaStyle,
                            levelOneParaStyle,
                            levelTwoParaStyle,
                            levelThreeParaStyle,
                            levelFourParaStyle]
        self.tableStyle = defaultTableStyle
        self._table = None
        self._entries = []
        self._lastEntries = []

    def beforeBuild(self):
        # keep track of the last run
        self._lastEntries = self._entries[:]
        self.clearEntries()

    def isIndexing(self):
        return 1

    def isSatisfied(self):
        return (self._entries == self._lastEntries)

    def notify(self, kind, stuff):
        """The notification hook called to register all kinds of events.

        Here we are interested in 'TOCEntry' events only.
        """
        if kind == 'TOCEntry':
            (level, text, pageNum) = stuff
            self.addEntry(level, text, pageNum)

    def clearEntries(self):
        self._entries = []

    def addEntry(self, level, text, pageNum):
        """Adds one entry to the table of contents.

        This allows incremental buildup by a doctemplate.
        Requires that enough styles are defined."""

        assert type(level) == type(1), "Level must be an integer"
        assert level < len(self.levelStyles), \
               "Table of contents must have a style defined " \
               "for paragraph level %d before you add an entry" % level

        self._entries.append((level, text, pageNum))

    def addEntries(self, listOfEntries):
        """Bulk creation of entries in the table of contents.

        If you knew the titles but not the page numbers, you could
        supply them to get sensible output on the first run."""

        for (level, text, pageNum) in listOfEntries:
            self.addEntry(level, text, pageNum)

    def wrap(self, availWidth, availHeight):
        "All table properties should be known by now."

        widths = (availWidth - self.rightColumnWidth,
                  self.rightColumnWidth)

        # makes an internal table which does all the work.
        # we draw the LAST RUN's entries!  If there are
        # none, we make some dummy data to keep the table
        # from complaining
        if len(self._lastEntries) == 0:
            _tempEntries = [(0,'Placeholder for table of contents',0)]
        else:
            _tempEntries = self._lastEntries

        tableData = []
        for (level, text, pageNum) in _tempEntries:
            leftColStyle = self.levelStyles[level]
            #right col style is right aligned
            rightColStyle = ParagraphStyle(name='leftColLevel%d' % level,
                                           parent=leftColStyle,
                                           leftIndent=0,
                                           alignment=enums.TA_RIGHT)
            leftPara = Paragraph(text, leftColStyle)
            rightPara = Paragraph(str(pageNum), rightColStyle)
            tableData.append([leftPara, rightPara])

        self._table = Table(tableData, colWidths=widths,
                            style=self.tableStyle)

        self.width, self.height = self._table.wrapOn(self.canv,availWidth, availHeight)
        return (self.width, self.height)

    def split(self, availWidth, availHeight):
        """At this stage we do not care about splitting the entries,
        we will just return a list of platypus tables.  Presumably the
        calling app has a pointer to the original TableOfContents object;
        Platypus just sees tables.
        """
        return self._table.splitOn(self.canv,availWidth, availHeight)

    def drawOn(self, canvas, x, y, _sW=0):
        """Don't do this at home!  The standard calls for implementing
        draw(); we are hooking this in order to delegate ALL the drawing
        work to the embedded table object.
        """
        self._table.drawOn(canvas, x, y, _sW)

class SimpleIndex(IndexingFlowable):
    """This creates a very simple index.

    Entries have a string key, and appear with a page number on
    the right.  Prototype for more sophisticated multi-level index."""
    def __init__(self):
        #keep stuff in a dictionary while building
        self._entries = {}
        self._lastEntries = {}
        self._table = None
        self.textStyle = ParagraphStyle(name='index',
                                        fontName='Times-Roman',
                                        fontSize=12)
    def isIndexing(self):
        return 1

    def isSatisfied(self):
        return (self._entries == self._lastEntries)

    def beforeBuild(self):
        # keep track of the last run
        self._lastEntries = self._entries.copy()
        self.clearEntries()

    def clearEntries(self):
        self._entries = {}

    def notify(self, kind, stuff):
        """The notification hook called to register all kinds of events.

        Here we are interested in 'IndexEntry' events only.
        """
        if kind == 'IndexEntry':
            (text, pageNum) = stuff
            self.addEntry(text, pageNum)

    def addEntry(self, text, pageNum):
        """Allows incremental buildup"""
        if self._entries.has_key(text):
            self._entries[text].append(str(pageNum))
        else:
            self._entries[text] = [pageNum]

    def split(self, availWidth, availHeight):
        """At this stage we do not care about splitting the entries,
        we will just return a list of platypus tables.  Presumably the
        calling app has a pointer to the original TableOfContents object;
        Platypus just sees tables.
        """
        return self._table.splitOn(self.canv,availWidth, availHeight)

    def wrap(self, availWidth, availHeight):
        "All table properties should be known by now."
        # makes an internal table which does all the work.
        # we draw the LAST RUN's entries!  If there are
        # none, we make some dummy data to keep the table
        # from complaining
        if len(self._lastEntries) == 0:
            _tempEntries = [('Placeholder for index',[0,1,2])]
        else:
            _tempEntries = self._lastEntries.items()
            _tempEntries.sort()

        tableData = []
        for (text, pageNumbers) in _tempEntries:
            #right col style is right aligned
            allText = text + ': ' + string.join(map(str, pageNumbers), ', ')


# -*- coding:utf-8 -*-
## src/history_window.py
##
## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
##                    Travis Shirk <travis AT pobox.com>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##

import gtk
import gobject
import time
import calendar

import gtkgui_helpers
import conversation_textview

from common import gajim
from common import helpers

from common.logger import Constants

constants = Constants()

# Completion dict
(
C_INFO_JID,
C_INFO_ACCOUNT,
C_INFO_NAME,
C_INFO_COMPLETION
) = range(4)

# contact_name, date, message, time
(
C_LOG_JID,
C_CONTACT_NAME,
C_UNIXTIME,
C_MESSAGE,
C_TIME
) = range(5)

class HistoryWindow:
	'''Class for browsing logs of conversations with contacts'''

	def __init__(self, jid = None, account = None):
		xml = gtkgui_helpers.get_glade('history_window.glade')
		self.window = xml.get_widget('history_window')
		self.jid_entry = xml.get_widget('jid_entry')
		self.calendar = xml.get_widget('calendar')
		scrolledwindow = xml.get_widget('scrolledwindow')
		self.history_textview = conversation_textview.ConversationTextview(
			account, used_in_history_window = True)
		scrolledwindow.add(self.history_textview.tv)
		self.history_buffer = self.history_textview.tv.get_buffer()
		self.history_buffer.create_tag('highlight', background = 'yellow')
		self.checkbutton = xml.get_widget('log_history_checkbutton')
		self.checkbutton.connect('toggled',
			self.on_log_history_checkbutton_toggled)
		self.query_entry = xml.get_widget('query_entry')
		self.query_combobox = xml.get_widget('query_combobox')
		self.query_combobox.set_active(0)
		self.results_treeview = xml.get_widget('results_treeview')
		self.results_window = xml.get_widget('results_scrolledwindow')

		# contact_name, date, message, time
		model = gtk.ListStore(str, str, str, str, str)
		self.results_treeview.set_model(model)
		col = gtk.TreeViewColumn(_('Name'))
		self.results_treeview.append_column(col)
		renderer = gtk.CellRendererText()
		col.pack_start(renderer)
		col.set_attributes(renderer, text = C_CONTACT_NAME)
		col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort
		col.set_resizable(True)

		col = gtk.TreeViewColumn(_('Date'))
		self.results_treeview.append_column(col)
		renderer = gtk.CellRendererText()
		col.pack_start(renderer)
		col.set_attributes(renderer, text = C_UNIXTIME)
		col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
		col.set_resizable(True)

		col = gtk.TreeViewColumn(_('Message'))
		self.results_treeview.append_column(col)
		renderer = gtk.CellRendererText()
		col.pack_start(renderer)
		col.set_attributes(renderer, text = C_MESSAGE)
		col.set_resizable(True)

		self.jid = None # The history we are currently viewing
		self.account = None
		self.completion_dict = {}
		self.accounts_seen_online = [] # Update dict when new accounts connect
		self.jids_to_search = []

		# This will load history too
		gobject.idle_add(self._fill_completion_dict().next)

		if jid:
			self.jid_entry.set_text(jid)

		xml.signal_autoconnect(self)
		self.window.show_all()

	def _fill_completion_dict(self):
		'''Fill completion_dict for key auto completion. Then load history for
		current jid (by calling another function).

		Key will be either jid or full_completion_name
		(contact name or long description like "pm-contact from groupchat....")

		{key : (jid, account, nick_name, full_completion_name}
		this is a generator and does pseudo-threading via idle_add()
		'''
		liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry)

		# Add all jids in logs.db:
		db_jids = gajim.logger.get_jids_in_db()
		self.completion_dict = dict.fromkeys(db_jids)

		self.accounts_seen_online = gajim.contacts.get_accounts()[:]

		# Enhance contacts of online accounts with contact. Needed for mapping below
		for account in self.accounts_seen_online:
			self.completion_dict.update(
				helpers.get_contact_dict_for_account(account))

		muc_active_img = gtkgui_helpers.load_icon('muc_active')
		contact_img = gajim.interface.jabber_state_images['16']['online']
		muc_active_pix = muc_active_img.get_pixbuf()
		contact_pix = contact_img.get_pixbuf()

		keys = self.completion_dict.keys()
		# Move the actual jid at first so we load history faster
		actual_jid = self.jid_entry.get_text().decode('utf-8')
		if actual_jid in keys:
			keys.remove(actual_jid)
			keys.insert(0, actual_jid)
		# Map jid to info tuple
		# Warning : This for is time critical with big DB
		for key in keys:
			completed = key
			contact = self.completion_dict[completed]
			if contact:
				info_name = contact.get_shown_name()
				info_completion = info_name
				info_jid = contact.jid
			else:
				# Corrensponding account is offline, we know nothing
				info_name = completed.split('@')[0]
				info_completion = completed
				info_jid = completed

			info_acc = self._get_account_for_jid(info_jid)

			if gajim.logger.jid_is_room_jid(completed) or\
			gajim.logger.jid_is_from_pm(completed):
				pix = muc_active_pix
				if gajim.logger.jid_is_from_pm(completed):
					# It's PM. Make it easier to find
					room, nick = gajim.get_room_and_nick_from_fjid(completed)
					info_completion = '%s from %s' % (nick, room)
					completed = info_completion
					info_name = nick
			else:
				pix = contact_pix

			liststore.append((pix, completed))
			self.completion_dict[key] = (info_jid, info_acc, info_name,
				info_completion)
			self.completion_dict[completed] = (info_jid, info_acc,
				info_name, info_completion)
			if key == actual_jid:
				self._load_history(info_jid, info_acc)
			yield True
		keys.sort()
		yield False

	def _get_account_for_jid(self, jid):
		'''Return the corresponding account of the jid.
		May be None if an account could not be found'''
		accounts = gajim.contacts.get_accounts()
		account = None
		for acc in accounts:
			jid_list = gajim.contacts.get_jid_list(acc)
			gc_list = gajim.contacts.get_gc_list(acc)
			if jid in jid_list or jid in gc_list:
				account = acc
				break
		return account

	def on_history_window_destroy(self, widget):
		self.history_textview.del_handlers()
		del gajim.interface.instances['logs']

	def on_history_window_key_press_event(self, widget, event):
		if event.keyval == gtk.keysyms.Escape:
			self.window.destroy()

	def on_close_button_clicked(self, widget):
		self.window.destroy()

	def on_jid_entry_activate(self, widget):
		if not self.query_combobox.get_active() < 0:
			# Don't disable querybox when we have changed the combobox
			# to GC or All and hit enter
			return
		jid = self.jid_entry.get_text().decode('utf-8')
		account = None # we don't know the account, could be any. Search for it!
		self._load_history(jid, account)
		self.results_window.set_property('visible', False)

	def on_jid_entry_focus(self, widget, event):
			widget.select_region(0, -1) # select text

	def _load_history(self, jid_or_name, account = None):
		'''Load history for the given jid/name and show it'''
		if jid_or_name and jid_or_name in self.completion_dict:
			# a full qualified jid or a contact name was entered
			info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name]
			self.jids_to_search = [info_jid]
			self.jid = info_jid

			if account:
				self.account = account
			else:
				self.account = info_account
			if self.account is None:
				# We don't know account. Probably a gc not opened or an
				# account not connected.
				# Disable possibility to say if we want to log or not
				self.checkbutton.set_sensitive(False)
			else:
				# Are log disabled for account ?
				if self.account in gajim.config.get_per('accounts', self.account,
					'no_log_for').split(' '):
					self.checkbutton.set_active(False)
					self.checkbutton.set_sensitive(False)
				else:
					# Are log disabled for jid ?
					log = True
					if self.jid in gajim.config.get_per('accounts', self.account,
						'no_log_for').split(' '):
						log = False
					self.checkbutton.set_active(log)
					self.checkbutton.set_sensitive(True)

			self.jids_to_search = [info_jid]

			# select logs for last date we have logs with contact
			self.calendar.set_sensitive(True)
			last_log = \
				gajim.logger.get_last_date_that_has_logs(self.jid, self.account)

			date = time.localtime(last_log)

			y, m, d = date[0], date[1], date[2]
			gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
			self.calendar.select_month(gtk_month, y)
			self.calendar.select_day(d)

			self.query_entry.set_sensitive(True)
			self.query_entry.grab_focus()

			title = _('Conversation History with %s') % info_name
			self.window.set_title(title)
			self.jid_entry.set_text(info_completion)

		else:	# neither a valid jid, nor an existing contact name was entered
			# we have got nothing to show or to search in
			self.jid = None
			self.account = None

			self.history_buffer.set_text('') # clear the buffer
			self.query_entry.set_sensitive(False)

			self.checkbutton.set_sensitive(False)
			self.calendar.set_sensitive(False)
			self.calendar.clear_marks()

			self.results_window.set_property('visible', False)

			title = _('Conversation History')
			self.window.set_title(title)

	def on_calendar_day_selected(self, widget):
		if not self.jid:
			return
		year, month, day = widget.get_date() # integers
		month = gtkgui_helpers.make_gtk_month_python_month(month)
		self._add_lines_for_date(year, month, day)

	def on_calendar_month_changed(self, widget):
		'''asks for days in this month if they have logs it bolds them (marks
		them)
		'''
		if not self.jid:
			return
		year, month, day = widget.get_date() # integers
		# in gtk January is 1, in python January is 0,
		# I want the second
		# first day of month is 1 not 0
		widget.clear_marks()
		month = gtkgui_helpers.make_gtk_month_python_month(month)
		weekday, days_in_this_month = calendar.monthrange(year, month)
		log_days = gajim.logger.get_days_with_logs(self.jid, year,
			month, days_in_this_month, self.account)
		for day in log_days:
			widget.mark_day(day)

	def _get_string_show_from_constant_int(self, show):
		if show == constants.SHOW_ONLINE:
			show = 'online'
		elif show == constants.SHOW_CHAT:
			show = 'chat'
		elif show == constants.SHOW_AWAY:
			show = 'away'
		elif show == constants.SHOW_XA:
			show = 'xa'
		elif show == constants.SHOW_DND:
			show = 'dnd'
		elif show == constants.SHOW_OFFLINE:
			show = 'offline'

		return show

	def _add_lines_for_date(self, year, month, day):
		'''adds all the lines for given date in textbuffer'''
		self.history_buffer.set_text('') # clear the buffer first
		self.last_time_printout = 0

		lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
		# lines holds list with tupples that have:
		# contact_name, time, kind, show, message
		for line in lines:
			# line[0] is contact_name, line[1] is time of message
			# line[2] is kind, line[3] is show, line[4] is message
			self._add_new_line(line[0], line[1], line[2], line[3], line[4])

	def _add_new_line(self, contact_name, tim, kind, show, message):
		'''add a new line in textbuffer'''
		if not message and kind not in (constants.KIND_STATUS,
			constants.KIND_GCSTATUS):
			return
		buf = self.history_buffer
		end_iter = buf.get_end_iter()

		if gajim.config.get('print_time') == 'always':
			timestamp_str = gajim.config.get('time_stamp')
			timestamp_str = helpers.from_one_line(timestamp_str)
			tim = time.strftime(timestamp_str, time.localtime(float(tim)))
			buf.insert(end_iter, tim) # add time
		elif gajim.config.get('print_time') == 'sometimes':
			every_foo_seconds = 60 * gajim.config.get(
				'print_ichat_every_foo_minutes')
			seconds_passed = tim - self.last_time_printout
			if seconds_passed > every_foo_seconds:
				self.last_time_printout = tim
				tim = time.strftime('%X ', time.localtime(float(tim)))
				buf.insert_with_tags_by_name(end_iter, tim + '\n',
					'time_sometimes')

		tag_name = ''
		tag_msg = ''

		show = self._get_string_show_from_constant_int(show)

		if kind == constants.KIND_GC_MSG:
			tag_name = 'incoming'
		elif kind in (constants.KIND_SINGLE_MSG_RECV,
		constants.KIND_CHAT_MSG_RECV):
			contact_name = self.completion_dict[self.jid][C_INFO_NAME]
			tag_name = 'incoming'
		elif kind in (constants.KIND_SINGLE_MSG_SENT,
		constants.KIND_CHAT_MSG_SENT):
			if self.account:
				contact_name = gajim.nicks[self.account]
			else:
				# we don't have roster, we don't know our own nick, use first
				# account one (urk!)
				account = gajim.contacts.get_accounts()[0]
				contact_name = gajim.nicks[account]
			tag_name = 'outgoing'
		elif kind == constants.KIND_GCSTATUS:
			# message here (if not None) is status message
			if message:
				message = _('%(nick)s is now %(status)s: %(status_msg)s') %\
					{'nick': contact_name, 'status': helpers.get_uf_show(show),
					'status_msg': message }
			else:
				message = _('%(nick)s is now %(status)s') % {'nick': contact_name,
					'status': helpers.get_uf_show(show) }
			tag_msg = 'status'
		else: # 'status'
			# message here (if not None) is status message
			if message:
				message = _('Status is now: %(status)s: %(status_msg)s') % \
					{'status': helpers.get_uf_show(show), 'status_msg': message}
			else:
				message = _('Status is now: %(status)s') % { 'status':
					helpers.get_uf_show(show) }
			tag_msg = 'status'

		# do not do this if gcstats, avoid dupping contact_name
		# eg. nkour: nkour is now Offline
		if contact_name and kind != constants.KIND_GCSTATUS:
			# add stuff before and after contact name
			before_str = gajim.config.get('before_nickname')
			before_str = helpers.from_one_line(before_str)
			after_str = gajim.config.get('after_nickname')
			after_str = helpers.from_one_line(after_str)
			format = before_str + contact_name + after_str + ' '
			buf.insert_with_tags_by_name(end_iter, format, tag_name)

		message = message + '\n'
		if tag_msg:
			self.history_textview.print_real_text(message, [tag_msg])
		else:
			self.history_textview.print_real_text(message)

	def on_query_entry_activate(self, widget):
		text = self.query_entry.get_text()
		model = self.results_treeview.get_model()
		model.clear()
		if text == '':
			self.results_window.set_property('visible', False)
			return
		else:
			self.results_window.set_property('visible', True)

		# perform search in preselected jids
		# jids are preselected with the query_combobox (all, single jid...)
		for jid in self.jids_to_search:
			account = self.completion_dict[jid][C_INFO_ACCOUNT]
			if account is None:
				# We do not know an account. This can only happen if the contact is offine,
				# or if we browse a groupchat history. The account is not needed, a dummy can
				# be set.
				# This may leed to wrong self nick in the displayed history (Uggh!)
				account = gajim.contacts.get_accounts()[0]

			# contact_name, time, kind, show, message, subject
			results = gajim.logger.get_search_results_for_query(
						jid, text, account)
			#FIXME:
			# add "subject:  | message: " in message column if kind is single
			# also do we need show at all? (we do not search on subject)
			for row in results:
				contact_name = row[0]
				if not contact_name:
					kind = row[2]
					if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :)
						contact_name = gajim.nicks[account]
					else:
						contact_name = self.completion_dict[jid][C_INFO_NAME]
				tim = row[1]
				message = row[4]
				local_time = time.localtime(tim)
				date = time.strftime('%Y-%m-%d', local_time)

				#  jid (to which log is assigned to), name, date, message,
				# time (full unix time)
				model.append((jid, contact_name, date, message, tim))

	def on_query_combobox_changed(self, widget):
		if self.query_combobox.get_active() < 0:
			return # custom entry
		self.account = None
		self.jid = None
		self.jids_to_search = []
		self._load_history(None) # clear textview

		if self.query_combobox.get_active() == 0:
			# JID or Contact name
			self.query_entry.set_sensitive(False)
			self.jid_entry.grab_focus()


# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

from __future__ import unicode_literals
import webnotes

from webnotes.utils import cstr, flt, cint
from webnotes.model.bean import getlist
from webnotes.model.code import get_obj
from webnotes import msgprint, _
import webnotes.defaults
from webnotes.model.mapper import get_mapped_doclist
from stock.utils import update_bin
from controllers.selling_controller import SellingController

class DocType(SellingController):
	def __init__(self, doc, doclist=[]):
		self.doc = doc
		self.doclist = doclist
		self.tname = 'Delivery Note Item'
		self.fname = 'delivery_note_details'
		self.status_updater = [{
			'source_dt': 'Delivery Note Item',
			'target_dt': 'Sales Order Item',
			'join_field': 'prevdoc_detail_docname',
			'target_field': 'delivered_qty',
			'target_parent_dt': 'Sales Order',
			'target_parent_field': 'per_delivered',
			'target_ref_field': 'qty',
			'source_field': 'qty',
			'percent_join_field': 'against_sales_order',
			'status_field': 'delivery_status',
			'keyword': 'Delivered'
		}]
	def on_update(self):
		#webnotes.errprint("hiee")
		#webnotes.errprint(self.doc.status_d)
		qry=webnotes.conn.sql("select priority from `tabDelivery Tracking` where status=%s",self.doc.status_d,as_list=1)
		#webnotes.errprint(qry[0][0])
		qr=webnotes.conn.sql("select count(priority) from `tabDelivery Tracking`",as_list=1)
		#webnotes.errprint(qr[0][0])
		if qry:
			p3= flt(qry[0][0])/flt(qr[0][0])
			p4=p3*100.00
			clr='white'
        	    	bclr='green'
            		w='100%'
			ww=cstr(p4)
            		per='%'
                	ww1=cint(ww)
               		ww2=cstr(ww1)+per
			a="

 "
                	f=" "+""+ww2+""+"

"
                	e=a+" "+b+" "+c+f
			self.doc.progress_bar=e
		self.doc.save()
		#webnotes.errprint(self.doc.progress_bar)
	def onload(self):
		billed_qty = webnotes.conn.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item`
			where docstatus=1 and delivery_note=%s""", self.doc.name)
		if billed_qty:
			total_qty = sum((item.qty for item in self.doclist.get({"parentfield": "delivery_note_details"})))
			self.doc.fields["__billing_complete"] = billed_qty[0][0] == total_qty

	def get_portal_page(self):
		return "shipment" if self.doc.docstatus==1 else None

	def set_actual_qty(self):
		for d in getlist(self.doclist, 'delivery_note_details'):
			if d.item_code and d.warehouse:
				actual_qty = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = '%s' and warehouse = '%s'" % (d.item_code, d.warehouse))
				d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0

	def so_required(self):
		"""check in manage account if sales order required or not"""
		if webnotes.conn.get_value("Selling Settings", None, 'so_required') == 'Yes':
			 for d in getlist(self.doclist,'delivery_note_details'):
				 if not d.against_sales_order:
					 msgprint("Sales Order No. required against item %s"%d.item_code)
					 raise Exception

	def validate(self):
		super(DocType, self).validate()

		import utilities
		utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Cancelled"])

		self.so_required()
		self.validate_proj_cust()
		self.check_stop_sales_order("against_sales_order")
		self.validate_for_items()
		self.validate_warehouse()
		self.validate_uom_is_integer("stock_uom", "qty")
		self.update_current_stock()
		self.validate_with_previous_doc()

		from stock.doctype.packed_item.packed_item import make_packing_list
		self.doclist = make_packing_list(self, 'delivery_note_details')

		self.doc.status = 'Draft'
		if not self.doc.installation_status: self.doc.installation_status = 'Not Installed'

	def validate_with_previous_doc(self):
		items = self.doclist.get({"parentfield": "delivery_note_details"})

		for fn in (("Sales Order", "against_sales_order"), ("Sales Invoice", "against_sales_invoice")):
			if items.get_distinct_values(fn[1]):
				super(DocType, self).validate_with_previous_doc(self.tname, {
					fn[0]: {
						"ref_dn_field": fn[1],
						"compare_fields": [["customer", "="], ["company", "="], ["project_name", "="],
							["currency", "="]],
					},
				})

				if cint(webnotes.defaults.get_global_default('maintain_same_sales_rate')):
					super(DocType, self).validate_with_previous_doc(self.tname, {
						fn[0] + " Item": {
							"ref_dn_field": "prevdoc_detail_docname",
							"compare_fields": [["export_rate", "="]],
							"is_child_table": True
						}
					})

	def validate_proj_cust(self):
		"""check for does customer belong to same project as entered.."""
		if self.doc.project_name and self.doc.customer:
			res = webnotes.conn.sql("select name from `tabProject` where name = '%s' and (customer = '%s' or ifnull(customer,'')='')"%(self.doc.project_name, self.doc.customer))
			if not res:
				msgprint("Customer - %s does not belong to project - %s. \n\nIf you want to use project for multiple customers then please make customer details blank in project - %s."%(self.doc.customer,self.doc.project_name,self.doc.project_name))
				raise Exception

	def validate_for_items(self):
		check_list, chk_dupl_itm = [], []
		for d in getlist(self.doclist,'delivery_note_details'):
			e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
			f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]

			if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == 'Yes':
				if e in check_list:
					msgprint("Please check whether item %s has been entered twice wrongly."
						% d.item_code)
				else:
					check_list.append(e)
			else:
				if f in chk_dupl_itm:
					msgprint("Please check whether item %s has been entered twice wrongly."
						% d.item_code)
				else:
					chk_dupl_itm.append(f)

	def validate_warehouse(self):
		for d in self.get_item_list():
			if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes":
				if not d['warehouse']:
					msgprint("Please enter Warehouse for item %s as it is stock item"
						% d['item_code'], raise_exception=1)

	def update_current_stock(self):
		for d in getlist(self.doclist, 'delivery_note_details'):
			bin = webnotes.conn.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
			d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0

		for d in getlist(self.doclist, 'packing_details'):
			bin = webnotes.conn.sql("select actual_qty, projected_qty from `tabBin` where item_code =	%s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
			d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
			d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0

	def on_submit(self):
		self.validate_packed_qty()

		# Check for Approving Authority
		get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)

		# update delivered qty in sales order
		self.update_prevdoc_status()

		# create stock ledger entry
		self.update_stock_ledger()

		self.credit_limit()

		self.make_gl_entries()

		# set DN status
		webnotes.conn.set(self.doc, 'status', 'Submitted')

	def on_cancel(self):
		self.check_stop_sales_order("against_sales_order")
		self.check_next_docstatus()

		self.update_prevdoc_status()

		self.update_stock_ledger()

		webnotes.conn.set(self.doc, 'status', 'Cancelled')
		self.cancel_packing_slips()

		self.make_cancel_gl_entries()

	def validate_packed_qty(self):
		"""
			Validate that if packed qty exists, it should be equal to qty
		"""
		if not any([flt(d.fields.get('packed_qty')) for d in self.doclist if
				d.doctype=='Delivery Note Item']):
			return
		packing_error_list = []
		for d in self.doclist:
			if d.doctype != 'Delivery Note Item': continue