"""
wxNotebook objects

@copyright: 2002-2007 Alberto Griggio <agriggio@users.sourceforge.net>
@license: MIT (see license.txt) - THIS PROGRAM COMES WITH NO WARRANTY
"""

import wx
import common
import wcodegen
import misc
from tree import Tree
from widget_properties import *
from edit_windows import ManagedBase, EditStylesMixin
from edit_sizers.edit_sizers import Sizer, SizerSlot
from xml_parse import XmlParsingError

try:
    from panel import EditPanel
    _has_panel = True
except ImportError:
    _has_panel = False


class NotebookVirtualSizer(Sizer):
    """\
    "Virtual sizer" responsible for the management of the pages of a Notebook.
    """
    def __init__(self, *args, **kwds):
        Sizer.__init__(self, *args, **kwds)
        self._itempos = 0

    def set_item(self, pos, option=None, flag=None, border=None, size=None,
                 force_layout=True):
        """\
        Updates the layout of the item at the given pos.
        """
        if not self.window.widget:
            return
        pos -= 1
        label, item = self.window.tabs[pos]
        if not item or not item.widget:
            return
        if not (pos < self.window.widget.GetPageCount()):
            self.window.widget.AddPage(item.widget, label)
        elif self.window.widget.GetPage(pos) is not item.widget:
            #self.window.widget.RemovePage(pos)
            self.window.widget.DeletePage(pos)
            self.window.widget.InsertPage(pos, item.widget, label)
            self.window.widget.SetSelection(pos)
            try:
                wx.CallAfter(item.sel_marker.update)
            except AttributeError:
                #self._logger.exception(_('Internal Error'))
                pass
        if self.window.sizer is not None:
            self.window.sizer.set_item(
                self.window.pos, size=self.window.widget.GetBestSize())

    def add_item(self, item, pos=None, option=0, flag=0, border=0, size=None,
                 force_layout=True):
        """\
        Adds an item to self.window.
        """
        #self._logger.debug('pos: %s, item.name: %s', pos, item.name)
        try:
            self.window.tabs[pos - 1][1] = item
        except IndexError:
            raise XmlParsingError(
                _('Notebook widget "%s" does not have any tabs!') %
                self.window.name
                )
        item._dont_destroy = True

    def free_slot(self, pos, force_layout=True):
        """\
        Replaces the element at pos with an empty slot
        """
        if self.window._is_removing_pages or not self.window.widget:
            return
        slot = SizerSlot(self.window, self, pos)
        #self._logger.debug('free: %s, %s, %s', slot, slot.pos, pos)
        slot.show_widget(True)
        pos -= 1
        label, item = self.window.tabs[pos]
        self.window.widget.RemovePage(pos)
        self.window.widget.InsertPage(pos, slot.widget, label)
        self.window.widget.SetSelection(pos)

    def get_itempos(self, attrs):
        """\
        Get position of sizer item (used in xml_parse)
        """
        self._itempos += 1
        return self._itempos

    def is_virtual(self):
        return True

# end of class NotebookVirtualSizer


class NotebookPagesProperty(GridProperty):
    def write(self, outfile, tabs):
        from xml.sax.saxutils import escape, quoteattr
        write = outfile.write
        write('    ' * tabs + '<tabs>\n')
        tab_s = '    ' * (tabs + 1)
        import widget_properties
        value = self.get_value()
        for i in range(len(value)):
            val = value[i]
            v = escape(common.encode_to_xml(val[0]))
            window = None
            try:
                t = self.owner.tabs[i]
                if t[0] == val[0]:
                    window = t[1]
            except:
                pass
            if window:
                write('%s<tab window=%s>' % (tab_s, quoteattr(window.name)))
                write(v)
                write('</tab>\n')
        write('    ' * tabs + '</tabs>\n')

# end of class NotebookPagesProperty


class TabsHandler(object):
    def __init__(self, parent):
        self.parent = parent
        self.tab_names = []
        self.curr_tab = []

    def start_elem(self, name, attrs):
        pass

    def end_elem(self, name):
        if name == 'tabs':
            self.parent.tabs = [[misc.wxstr(name), None] for name in
                                self.tab_names]
            self.parent.properties['tabs'].set_value([[name] for name in
                                                      self.tab_names])
            return True
        elif name == 'tab':
            self.tab_names.append("".join(self.curr_tab))
            self.curr_tab = []
        return False

    def char_data(self, data):
        self.curr_tab.append(data)

# end of class TabsHandler


class EditNotebook(ManagedBase, EditStylesMixin):
    """\
    Class to handle wxNotebook objects
    """

    _custom_base_classes = True

    notebook_number = 1
    """\
    @cvar: Next free number for notebook names. The number is continuously.

    Each notebook gets an own number. It's very useful for labeling
    panes. Deleting notebooks won't decrease this number.

    @see: L{self.next_notebook_name()}
    @type: Integer
    @note: Use only C{+=1} for increasing!
    """

    pane_number = 1
    """\
    @ivar: Free number for pane names. This number is is continuously.

    Each pane get an own number. It's very useful for labeling. Deleting
    ones won't decrease this number.

    @see: L{self.next_pane_name()}
    @type: Integer
    """

    events = ['EVT_NOTEBOOK_PAGE_CHANGED', 'EVT_NOTEBOOK_PAGE_CHANGING']

    update_widget_style = False

    def __init__(self, name, parent, id, style, sizer, pos,
                 property_window, show=True):
        # create new and (still) unused notebook name
        if not name:
            name = self.next_notebook_name()

        # increase number of created notebooks
        EditNotebook.notebook_number += 1

        # initialise parent classes
        ManagedBase.__init__(self, name, 'wxNotebook', parent, id, sizer,
                             pos, property_window, show=show)
        EditStylesMixin.__init__(self)

        # initialise instance variables
        self.set_style(style)
        self.virtual_sizer = NotebookVirtualSizer(self)
        self._is_removing_pages = False

        # initialise properties remaining staff
        self.tabs = [['tab1', None]]  # list of pages of this notebook
                                      # (actually a list of
                                      #  2-list label, window)
        self.access_functions['style'] = (self.get_style, self.set_style)
        self.properties['style'] = CheckListProperty(
            self, 'style', self.widget_writer)
        self.access_functions['tabs'] = (self.get_tabs, self.set_tabs)
        tab_cols = [('Tab label', GridProperty.STRING)]
        self.properties['tabs'] = NotebookPagesProperty(
            self, 'tabs', None, tab_cols, label=_("Tabs"),
            can_remove_last=False, )
        del tab_cols
        self.nb_sizer = None
        self._create_slots = False

        self.no_custom_class = False
        self.access_functions['no_custom_class'] = (self.get_no_custom_class,
                                                    self.set_no_custom_class)
        self.properties['no_custom_class'] = CheckBoxProperty(
            self, 'no_custom_class',
            label=_("Don't generate code for this custom class"))

        # first pane should have number 1
        self.pane_number = 1

    def create_widget(self):
        self.widget = wx.Notebook(
            self.parent.widget, self.id, style=self.get_int_style())

    def show_widget(self, yes):
        ManagedBase.show_widget(self, yes)
        if self._create_slots:
            self._create_slots = False
            for i in range(len(self.tabs)):
                if self.tabs[i][1] is None:
                    self.tabs = self.tabs[:i]
                    self.properties['tabs'].set_value(self.get_tabs())

    def finish_widget_creation(self):
        ManagedBase.finish_widget_creation(self)
        # replace 'self' with 'self.nb_sizer' in 'self.sizer'

    def create_properties(self):
        ManagedBase.create_properties(self)
        panel = wx.ScrolledWindow(
            self.notebook,
            wx.ID_ANY,
            style=wx.TAB_TRAVERSAL,
            )
        self.properties['no_custom_class'].display(panel)
        self.properties['style'].display(panel)
        self.properties['tabs'].display(panel)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.properties['no_custom_class'].panel, 0,
                  wx.ALL | wx.EXPAND, 3)
        sizer.Add(self.properties['style'].panel, 0, wx.EXPAND)
        sizer.Add(self.properties['tabs'].panel, 1, wx.ALL | wx.EXPAND, 3)
        panel.SetAutoLayout(True)
        panel.SetSizer(sizer)
        sizer.Fit(panel)
        self.notebook.AddPage(panel, _('Widget'))
        self.properties['tabs'].set_col_sizes([-1])

    def on_set_focus(self, event):
        self.show_properties()
        event.Skip()

    def _add_tab(self, window, pos):
        if window is None:
            window = SizerSlot(self, self.virtual_sizer, pos)
            self.tabs[pos - 1][1] = window
        else:
            window._dont_destroy = True
            node = Tree.Node(window)
            window.node = node
            common.app_tree.add(node, self.node)
        if self.widget:
            window.show_widget(True)
            self.virtual_sizer.set_item(pos)
            try:
                wx.CallAfter(window.sel_marker.update)
            except AttributeError:
                #self._logger.exception(_('Internal Error'))
                pass

    def get_tabs(self):
        return [[n] for n, w in self.tabs]

    def set_tabs(self, tabs):
        delta = len(self.tabs) - len(tabs)
        if delta > 0:
            self._is_removing_pages = True
            # we have to remove some pages
            i = len(tabs)
            if self.widget:
                for n, window in self.tabs[i:]:
                    self.widget.RemovePage(i)
                    window.remove(False)
            del self.tabs[i:]
            if self.widget:
                self.widget.SetSelection(0)
            self._is_removing_pages = False
        elif delta < 0:
            # we have to add some pages
            pos = len(self.tabs)
            for i in range(-delta):
                self.tabs.append(['', None])
                pos += 1
                if _has_panel:
                    window = EditPanel(
                        self.next_pane_name(),
                        self,
                        wx.ID_ANY,
                        self.virtual_sizer,
                        pos,
                        self.property_window,
                        )
                    self._add_tab(window, pos)
                else:
                    self._add_tab(None, pos)
            if self.widget:
                self.widget.SetSelection(self.widget.GetPageCount() - 1)
        # finally, we must update the labels of the tabs
        for i in range(len(tabs)):
            tt = misc.wxstr(tabs[i][0])
            if self.widget:
                self.widget.SetPageText(i, tt)
            self.tabs[i][0] = tt

    def delete(self):
        if self.widget:
            self.widget.DeleteAllPages()
        ManagedBase.delete(self)

    def get_property_handler(self, name):
        if name == 'tabs':
            return TabsHandler(self)
        return ManagedBase.get_property_handler(self, name)

    def find_page(self, page):
        """\
        returns the index of the given page in the notebook, or -1 if the page
        cannot be found
        """
        if not self.widget:
            return -1
        for i in range(len(self.tabs)):
            if self.tabs[i][1] is page:
                if i < self.widget.GetPageCount():
                    return i
                else:
                    return -1
        return -1

    def get_no_custom_class(self):
        return self.no_custom_class

    def set_no_custom_class(self, value):
        self.no_custom_class = bool(int(value))

    def next_notebook_name(self):
        """\
        Return new and (still) unused notebook name

        @see: L{self.notebook_number}
        """
        while True:
            name = 'notebook_%d' % EditNotebook.notebook_number
            if not common.app_tree.has_name(name):
                break
            EditNotebook.notebook_number += 1
        return name

    def next_pane_name(self):
        """\
        Return new and (still) unused pane name

        @see: L{self.pane_number}
        """
        while True:
            pane_name = "%s_pane_%d" % (self.name, self.pane_number)
            self.pane_number += 1
            if not common.app_tree.has_name(pane_name):
                break
        return pane_name

# end of class EditNotebook


editor_class = EditNotebook
editor_icon = 'icons/notebook.xpm'
editor_name = 'EditNotebook'
editor_style = ''

dlg_title = _('wxNotebook')
box_title = _('Orientation')
choices = 'wx.NB_TOP|wx.NB_BOTTOM|wx.NB_LEFT|wx.NB_RIGHT'
tmpl_label = 'notebook'


def builder(parent, sizer, pos, number=[1]):
    """\
    Factory function for editor objects from GUI.
    """
    dialog = wcodegen.WidgetStyleSelectionDialog(
        dlg_title, box_title, choices)

    if not dialog.ShowModal() == wx.ID_OK:
        return
    widget = editor_class(None, parent, wx.ID_ANY, dialog.get_selection(),
                          sizer, pos, common.property_panel, show=False)
    if _has_panel:
        pane1 = EditPanel(widget.next_pane_name(), widget, wx.ID_ANY,
                          widget.virtual_sizer, 1, common.property_panel)

    node = Tree.Node(widget)
    widget.node = node
    widget.virtual_sizer.node = node

    widget.set_option(1)
    widget.set_style("wxEXPAND")
    widget.show_widget(True)
    common.app_tree.insert(node, sizer.node, pos - 1)

    if _has_panel:
        widget._add_tab(pane1, 1)

    sizer.set_item(widget.pos, 1, wx.EXPAND)


def xml_builder(attrs, parent, sizer, sizeritem, pos=None):
    """\
    Factory to build editor objects from a XML file
    """
    from xml_parse import XmlParsingError
    try:
        name = attrs['name']
    except KeyError:
        raise XmlParsingError(_("'name' attribute missing"))
    if sizer is None or sizeritem is None:
        raise XmlParsingError(_("sizer or sizeritem object cannot be None"))
    widget = editor_class(name, parent, wx.ID_ANY, editor_style, sizer,
                          pos, common.property_panel)
    sizer.set_item(widget.pos, option=sizeritem.option,
                   flag=sizeritem.flag, border=sizeritem.border)
    node = Tree.Node(widget)
    widget.node = node
    if pos is None:
        common.app_tree.add(node, sizer.node)
    else:
        common.app_tree.insert(node, sizer.node, pos - 1)
    return widget


def initialize():
    """\
    initialization function for the module: returns a wxBitmapButton to be
    added to the main palette.
    """
    common.widgets[editor_name] = builder
    common.widgets_from_xml[editor_name] = xml_builder
    return common.make_object_button(editor_name, editor_icon)
