# GNU Enterprise Forms - Curses UI Driver - Menu Widget
#
# Copyright 2001-2009 Free Software Foundation
#
# This file is part of GNU Enterprise
#
# GNU Enterprise 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 3, or (at your option) any later version.
#
# GNU Enterprise 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 program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# $Id: menu.py 9956 2009-10-11 18:54:57Z reinhard $

import curses

from gnue.forms.uidrivers.curses.widgets._base import UIHelper


# =============================================================================
# Wrap an UI layer around a wxMenu widget
# =============================================================================

class UIMenu(UIHelper):
    """
    Implements a menu object.
    """

    enough_space = True

    # -------------------------------------------------------------------------
    # Create a menu widget
    # -------------------------------------------------------------------------

    def _create_widget_(self, event, spacer):
        """
        Creates a new Menu widget.
        """

        self.hotkey = None
        if self._gfObject.name == '__main_menu__' \
                and not self._form._features['GUI:MENUBAR:SUPPRESS']:
            # Menu bar of the form
            self._uiForm.register_main_menu(self)
        else:
            # Submenu or popup menu
            if isinstance (event.container, UIMenu):
                event.container.add_subitem(self)

        self.is_enabled = True
        self._container = self
        self.__menu_items = []

        return None


    # -------------------------------------------------------------------------
    # Add a sub item to the menu
    # -------------------------------------------------------------------------

    def add_subitem(self, item):

        self.__menu_items.append(item)


    # -------------------------------------------------------------------------
    # Show this menu
    # -------------------------------------------------------------------------

    def show(self, page, offsx=0, offsy=0, run=True):

        self.__page = page
        self.__pattern = self.__get_pattern()
        self.__sep_len = len(self.__pattern % {'text': '-', 'hotk': '-'})
        self.__separator = curses.ACS_HLINE

        self.__data = []
        for item in self.__menu_items:
            if item._gfObject.label:
                add = self.__pattern % \
                        {'text': item._gfObject.label,
                         'hotk': item.hotkey or ''}
                if getattr(item, 'check', False):
                    checked = item.is_checked and '*' or ' '
                else:
                    checked = ' '

                self.__data.append('%s%s ' % (checked, add))
            else:
                self.__data.append(self.__separator)

        # Figure out where to position the menu
        ca_left, ca_top, ca_right, ca_bottom = self._uiForm.get_canvas()
        if not offsx:
            offsx = ca_left
        if not offsy:
            offsy = ca_top

        maxy, maxx = ca_bottom, ca_right
        move_y = 0
        # Can we add it to the right side
        if offsx + self.__sep_len + 4 < maxx:
            self.left = offsx

        # Hm, so try to add it to the left side, but make sure to move the
        # vertical position by a line (otherwise it would hide the linking menu
        # entry)
        elif offsx - self.__sep_len - 4 > 0:
            self.left = offsx - self.__sep_len - 4
            move_y = 1

        # Ok, there seems to be no place for this menu, so just align it at the
        # left margin.
        else:
            self.left = 0
            move_y = 1

        # Vertical position: is there enough space below the starting row
        if offsy + move_y + len(self.__data) + 2 < maxy:
            self.top = offsy + move_y

        elif offsy - move_y - len(self.__data) - 2 > 0:
            self.top = offsy - move_y - len(self.__data) - 2
        else:
            self.top = 0

        self.width = min((self.__sep_len + 4), maxx - self.left)
        self.height = min(len(self.__data) + 2, maxy - self.top)

        self.offset = 0
        self.selected = 0
        self.display = 0

        self.__draw_menu(run)

        if run:
            result = self.run()
        else:
            result = False

        return result


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

    def __get_pattern(self):

        ca_left, ca_top, ca_right, ca_bottom = self._uiForm.get_canvas()
        # Take away the decoration of the menu
        av_width = ca_right - ca_left - 4

        text = []
        hotk = []

        for item in self.__menu_items:
            add = item._gfObject.label or ''
            text.append(add)
            hotk.append(item.hotkey or '')

        max_text = max([len(i) for i in text])
        max_hotk = max([len(i) for i in hotk])

        # Get the best width for this menu
        best = max_text
        if max_hotk:
            best += 3 + max_hotk
            patt = "%%(text)-%ds%%(hotk)%ds" % (max_text, max_hotk + 3)
        else:
            patt = "%%(text)-%ds" % max_text

        if best <= av_width:
            result = patt

        # Does it help to reduce the gap between text and hotkey
        elif max_hotk and best <= av_width - 2:
            result = "%%(text)-%ds%%(hotk)%ds" % (max_text, max_hotk + 1)

        # And if we use the text only 
        elif max_text <= av_width:
            result = "%%(text)-%ds" % max_text

        else:
            result = "%%(text)-%ds" % av_width

        return result


    # -------------------------------------------------------------------------
    # Draw the menu
    # -------------------------------------------------------------------------

    def __draw_menu(self, highlight):

        self.__window = curses.newwin(self.height, self.width, self.top,
                self.left)
        self.__window.keypad(1)
        self.__window.box()

        self.__normal = self._uiDriver.attr['entry']
        self.__reverse = self._uiDriver.attr['focusentry']
        self.__disabled = self._uiDriver.attr['disabled']

        self.__window.bkgd(' ', self.__normal)

        count = self.height - 2
        data = self.__data[self.offset:self.offset+count]
        if len(data) < count:
            data.extend([''] * (count - len(data)))

        for row, value in enumerate(data):
            mitem = self.__menu_items[self.offset+row]
            if mitem.is_enabled:
                attr = self.__normal
            else:
                attr = self.__disabled

            if highlight and row == self.display:
                if mitem.is_enabled:
                    attr = self.__reverse
                else:
                    attr = self._uiDriver.attr['focusdisabled']

            if isinstance(value, basestring):
                text = value.ljust(self.width - 2)[:self.width - 2]
                self.__window.addstr(1 + row, 1, o(text), attr)
            else:
                self.__window.addch(1 + row, 1, ' ', attr)
                self.__window.hline(1 + row, 2, curses.ACS_HLINE | attr,
                        self.width - 4)
                self.__window.addch(1 + row, self.width - 2, ' ', attr)

        self.__window.refresh()


    # -------------------------------------------------------------------------
    # Start an input loop for the menu
    # -------------------------------------------------------------------------

    def run(self):

        old = curses.curs_set(0)
        previous = None

        try:
            while True:
                result = True
                if isinstance(self.__menu_items[self.selected], UIMenu):
                    previous = self.__menu_items[self.selected]
                    self.__step_down(previous, False)

                key = self.__window.getch()
                current = self.__menu_items[self.selected]

                if key == 27:
                    break

                if key in [curses.KEY_UP, curses.KEY_DOWN]:
                    if previous is not None:
                        self.__update_to_current()

                    self.__move([1, -1][key == curses.KEY_UP])
                    key = None

                elif key in [curses.KEY_RIGHT, curses.KEY_ENTER, 10]:

                    if isinstance(current, UIMenu):
                        quit = self.__step_down(current, True)
                        if quit:
                            break

                    elif key != curses.KEY_RIGHT:
                        # Don't fire an event on separators
                        if current._gfObject.label is not None:
                            if current._event_fire():
                                break

                elif key == curses.KEY_LEFT:
                    result = False
                    break

                self.__draw_menu(True)

        finally:
            curses.curs_set(old)
            return result



    # -------------------------------------------------------------------------
    # Repaint all parent menus
    # -------------------------------------------------------------------------

    def __update_to_current(self):

        self.__page.repaint(False)

        level = self
        while isinstance(level, UIMenu):
            level.repaint()
            level = level.getParent()

        curses.doupdate()


    # -------------------------------------------------------------------------
    # Show and optionally run a submenu
    # -------------------------------------------------------------------------

    def __step_down(self, destination, run):

        offsx = self.left + self.width - 1
        offsy = self.top + self.display + 1 
        return destination.show(self.__page, offsx, offsy, run)


    # -------------------------------------------------------------------------
    # Move the selected item
    # -------------------------------------------------------------------------

    def __move(self, direction):

        self.display += direction
        self.selected += direction

        if self.display >= self.height - 2:
            if self.selected < len(self.__data):
                self.offset += 1

        elif self.display < 0:
            self.offset = max(0, self.offset - 1)

        self.selected = max(0, self.selected)
        self.selected = min(len(self.__data) - 1, self.selected)

        self.display = max(0, self.display)
        self.display = min(self.display, self.height-3)


    # -------------------------------------------------------------------------
    # Repaint the menu
    # -------------------------------------------------------------------------

    def repaint(self):

        self.__draw_menu(False)


# =============================================================================
# Configuration data
# =============================================================================

configuration = {
  'baseClass': UIMenu,
  'provides' : 'GFMenu',
  'container': 1,
}
