# GNU Enterprise Forms - Curses UI Driver - Form Widget
#
# Copyright 2000-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: form.py 10017 2009-10-28 14:59:53Z reinhard $

import curses
import os.path

from gnue.forms.input.GFKeyMapper import KeyMapper

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

__all__ = ['UIForm']

# =============================================================================
# Form class
# =============================================================================

class UIForm(UIHelper):

    # -------------------------------------------------------------------------
    # Initialization
    # -------------------------------------------------------------------------

    def __init__(self, event):

        UIHelper.__init__(self, event)

        self.__pages = []
        self.__currentPage = None
        self.__main_menu = None
        self.__hotkeys = {}
        self.__close_in_progress = False

        # Status bar values
        self.__tip = ''
        self.__status = ''
        self.__insert = ''
        self.__cur_rec = 0
        self.__max_rec = 0
        self.__cur_page = 0
        self.__max_page = 0


    # -------------------------------------------------------------------------
    # Initialize form
    # -------------------------------------------------------------------------

    def _create_widget_(self, event, spacer):

        self.__window = None
        self._container = self


    # -------------------------------------------------------------------------
    # Virtual methods
    # -------------------------------------------------------------------------

    def _container_is_ready_(self):

        return self.__window is not None

    # -------------------------------------------------------------------------
    # Register an UIMenu instance as main menu
    # -------------------------------------------------------------------------

    def register_main_menu(self, menu):

        self.__main_menu = menu


    # -------------------------------------------------------------------------
    # Register a menu item with a given set of hotkeys
    # -------------------------------------------------------------------------

    def register_hotkey(self, item, keycodes):
        """
        Register a menu item with a set of hotkeys

        @param item: the UIMenuItem to assocatiate with the hotkeys
        @param keycodes: tuple with the keycodes of the hotkey
        """

        for key in keycodes:
            self.__hotkeys[key] = item


    # -------------------------------------------------------------------------
    # Show the form
    # -------------------------------------------------------------------------

    def _ui_show_(self, modal):

        (width, height) = self._uiDriver.screen_size()
        self.set_size_and_fit(width, height)
        self._uiDriver.show_form(self, modal)


    # -------------------------------------------------------------------------
    # Set form title
    # -------------------------------------------------------------------------

    def _ui_set_title_(self, title):

        if not self.__window:
            return

        (y, x) = self.__window.getmaxyx()

        text = ' ' * ((x - len(title)) / 2) + title

        self.__window.bkgdset(' ', self._uiDriver.attr ['title'])
        self.__window.addstr(0, 0, o(text))
        self.__window.clrtoeol()
        self.__window.refresh(0, 0, 0, 0, 0, x)

    # -------------------------------------------------------------------------
    # User feedback functions
    # -------------------------------------------------------------------------

    def _ui_begin_wait_(self):
        self.status_message(u_("processing..."))

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

    def _ui_end_wait_(self):
        self.status_message(None)

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

    def _ui_beep_(self):
        curses.beep()

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

    def _ui_update_status_(self, tip, status, insert, cur_rec, max_rec,
            cur_page, max_page):

        # Gets called with incomplete parameters, so we always have to remember
        # the old values for missing parameters
        if tip     is not None: self.__tip     = tip
        if status  is not None: self.__status  = status
        if insert  is not None: self.__insert  = insert
        if cur_rec  is not None: self.__cur_rec  = cur_rec
        if max_rec  is not None: self.__max_rec  = max_rec
        if cur_page is not None: self.__cur_page = cur_page
        if max_page is not None: self.__max_page = max_page

        self.__update_status_bar()

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

    def _ui_show_message_(self, message, kind, title, cancel):

        return self._uiDriver.show_message(message, kind, cancel)

    # -------------------------------------------------------------------------
    # Print form screenshot
    # -------------------------------------------------------------------------

    def _ui_printout_(self, title, subtitle, user):

        pass

    # -------------------------------------------------------------------------
    # Show a file selection dialog
    # -------------------------------------------------------------------------

    def _ui_select_files_(self, title, default_dir, default_file, wildcard,
            mode, multiple, overwrite_prompt, file_must_exist):
        """
        Bring up a dialog for selecting filenames.

        @param title: Message to show on the dialog
        @param default_dir: the default directory, or the empty string
        @param default_file: the default filename, or the empty string
        @param wildcard: a list of tuples describing the filters used by the
            dialog.  Such a tuple constists of a description and a fileter.
            Example: [('PNG Files', '*.png'), ('JPEG Files', '*.jpg')]
            If no wildcard is given, all files will match (*.*)
        @param mode: Is this dialog an open- or a save-dialog.  If mode is
            'save' it is a save dialog, everything else would be an
            open-dialog.
        @param multiple: for open-dialog only: if True, allows selecting
            multiple files
        @param overwrite_prompt: for save-dialog only: if True, prompt for a
            confirmation if a file will be overwritten
        @param file_must_exist: if True, the user may only select files that
            actually exist

        @returns: a sequence of filenames or None if the dialog has been
            cancelled.
        """

        defname = ''
        if default_dir or default_file:
            defname = os.path.join(default_dir, default_file)

        while True:
            filename = self.ask(u_("Filename"), defname)
            if mode.lower().startswith('save'):
                if os.path.exists(filename) and overwrite_prompt:
                    ovw = self.ask(u_("File '%s' already exists. Overwrite") \
                            % filename, u_("n"), [u_("y"), u_("n")])
                    if ovw == u_("y"):
                        break
                else:
                    break
            else:
                if file_must_exist and not os.path.exists(filename):
                    self.ask(u_("File '%s' does not exist.") % filename, "",
                            [chr(10)])
                else:
                    break

        if filename:
            result = [filename]
        else:
            result = None

        return result

    # -------------------------------------------------------------------------
    # Show a directory selection box
    # -------------------------------------------------------------------------

    def _ui_select_dir_(self, title, default_dir, new_dir=False):
        """
        Bring up a dialog for selecting a directory path.

        @param title: Message to show on the dialog
        @param default_dir: the default directory, or the empty string
        @param new_dir: If true, add "Create new directory" button and allow
            directory names to be editable. On Windows the new directory button
            is only available with recent versions of the common dialogs.

        @returns: a path or None if the dialog has been cancelled.
        """

        dirname = self.ask(u_("Directory"), default_dir)
        if not dirname:
           dirname = None

        return dirname


    # -------------------------------------------------------------------------
    # Output a message on the status bar
    # -------------------------------------------------------------------------

    def status_message(self, message):

        if not self.ready():
            return

        gDebug(2, "StatusMessage: %s" % message)
        if message:
            (y, x) = self.__window.getmaxyx()
            self.__window.bkgdset(' ', self._uiDriver.attr['status'])
            self.__window.addstr(y - 2, 0, o(message))
            self.__window.clrtoeol()
            self.__window.refresh(y - 2, 0, y - 2, 0, y - 2, x)
        else:
            self.__update_status_bar()

    # -------------------------------------------------------------------------
    # Go to a specific page
    # -------------------------------------------------------------------------

    def _ui_goto_page_(self, page):

        self.__currentPage = page
        self.__update_page_list()

    # -------------------------------------------------------------------------
    # Add a page to the form
    # -------------------------------------------------------------------------

    def add_page(self, page, caption):
        self.__pages.append((page, caption))
        if not self.__currentPage:
            self.__currentPage = page
        self.__update_page_list()

    # -------------------------------------------------------------------------
    # Update screen and wait for user input
    # -------------------------------------------------------------------------

    def wait(self):

        result = None
        while result is None:
            result = self.__currentPage.wait()
            if result == curses.KEY_F10 and self.__main_menu is not None:
                self.__main_menu.show(self.__currentPage)
                result = None

            if result in self.__hotkeys:
                gDebug(2, "MenuItem is called via hotkey: %r" % result)
                self.__hotkeys[result]._event_fire()
                result  = None

            if self.__close_in_progress:
                break

        return result


    # -------------------------------------------------------------------------
    # Get free area in the window
    # -------------------------------------------------------------------------

    def get_canvas(self):

        if self.ready():
            (y, x) = self.__window.getmaxyx()
            return (0, 2, x, y - 2)
        else:
            return None

    # -------------------------------------------------------------------------
    # Update page list
    # -------------------------------------------------------------------------

    def __update_page_list(self):

        if not self.ready():
            return

        self.__window.bkgdset(' ', self._uiDriver.attr ['page'])
        self.__window.move(1, 0)

        for (page, caption) in self.__pages:
            if caption is None:
                caption = ' '
            if page == self.__currentPage:
                self.__window.addstr('[' + o(caption) + ']',
                                      self._uiDriver.attr ['currentpage'])
            else:
                self.__window.addstr('[' + o(caption) + ']')

        self.__window.clrtoeol()

        (y, x) = self.__window.getmaxyx()
        self.__window.refresh(1, 0, 1, 0, 1, x)


    # -------------------------------------------------------------------------
    # Update status bar
    # -------------------------------------------------------------------------

    def __update_status_bar(self):

        if not self.ready():
            return

        try:
            (y, x) = self.__window.getmaxyx()
            self.__window.bkgdset(' ', self._uiDriver.attr ['status'])

            tip = ('%-' + str(x - 25) + 's') % self.__tip

            if self.__cur_rec == 0 or self.__max_rec == 0:
                recstr = ''
            else:
                recstr = '%d/%d' % (self.__cur_rec, self.__max_rec)

            if self.__cur_page == 0 or self.__max_page == 0:
                pagestr = ''
            else:
                pagestr = '%d/%d' % (self.__cur_page, self.__max_page)

            self.__window.addstr(y - 2, 0,      '%s'   % o(tip))
            self.__window.addstr(y - 2, x - 24, '%-4s' % o(self.__status))
            self.__window.addstr(y - 2, x - 19, '%-3s' % o(self.__insert))
            self.__window.addstr(y - 2, x - 15, '%-9s' % recstr)
            self.__window.addstr(y - 2, x -  5, '%-5s' % pagestr)

            self.__window.addch(y - 2, x - 25, curses.ACS_VLINE)
            self.__window.addch(y - 2, x - 20, curses.ACS_VLINE)
            self.__window.addch(y - 2, x - 16, curses.ACS_VLINE)
            self.__window.addch(y - 2, x -  6, curses.ACS_VLINE)

            self.__window.refresh(y - 2, 0, y - 2, 0, y - 1, x)

        except curses.error:
            pass

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

    def update_fkey_bar(self):

        if not self.ready():
            return

        (y, x) = self.__window.getmaxyx()

        self.__window.bkgdset(' ', self._uiDriver.attr['fkeys'])

        text = []
        for key in range(curses.KEY_F1, curses.KEY_F13):
            menuitem = self.__hotkeys.get(key)
            if menuitem is not None and menuitem.is_enabled:
                text.append(menuitem.hotkey + "=" + menuitem._gfObject.label)

        self.__window.addstr(y-1, 0, o(' '.join(text)))
        self.__window.clrtoeol()
        self.__window.refresh(y - 1, 0, y - 1, 0, y, x)

    # -------------------------------------------------------------------------
    # Close the form
    # -------------------------------------------------------------------------

    def _ui_close_(self):

        self.__close_in_progress = True

    # -------------------------------------------------------------------------
    # Create the window and fit all widgets into the available space
    # -------------------------------------------------------------------------

    def set_size_and_fit(self, width, height):
        """
        Create the window for the form and 
        """

        self.__window = curses.newpad(height, width)
        self.__window.keypad(1)

        self._ui_set_title_(self._gfObject.title)

        # The children of a form are pages which can have all the available
        # space
        pages_ok = True
        (left, top, right, bottom) = self.get_canvas()
        for child in self._children:
            child.left = left
            child.top = top
            child.set_size_and_fit(right-left, bottom-top)
            pages_ok &= child.enough_space

        try:
            self.__update_page_list()
            self.__update_status_bar()
            self.update_fkey_bar()

        except curses.error:
            pass

        if pages_ok:
            self._uiDriver._focus_widget._ui_set_focus_(0)


    # -------------------------------------------------------------------------
    # Refresh the current form
    # -------------------------------------------------------------------------

    def refresh(self):
        """
        Repaint the current form
        """
        try:
            (y, x) = self.__window.getmaxyx()
            self.__window.refresh(0, 0, 0, 0, y, x)

        except curses.error:
            pass

    # -------------------------------------------------------------------------
    # Ask the user a question
    # -------------------------------------------------------------------------

    def ask(self, label, default, opts=[]):

        (y, x) = self.__window.getmaxyx()
        win = curses.newwin(2, x, y-2, 0)
        win.keypad(1)
        win.bkgd(' ', self._uiDriver.attr['status'])

        text = label
        if opts == [chr(10)]:
            opts = []
            text += " " + u_("Hit <Return> to continue.")
        else:
            if opts:
                text += " (%s)" % ",".join(opts)
            text += ":"

        win.addstr(0, 1, text)
        win.refresh()

        while True:
            answer = self.__accept(win, default, len(text) + 2, 0,
                x - len(text) - 3, x, y)

            if (answer is None) or (opts and answer in opts) or (not opts):
                break

        return answer


    # -------------------------------------------------------------------------
    # Get a string from keyboard input
    # -------------------------------------------------------------------------

    def __accept(self, window, default, x, y, maxlen, wx, wy):

        result = default
        old = curses.curs_set(1)
        try:
            gDebug(2, "x/y= %s/%s" % (x, y))
            window.move(y, x)
            pos = len(result)

            while True:
                txt = result.ljust(maxlen)[:maxlen]
                window.addstr(y, x, txt, self._uiDriver.attr['focusentry'])
                window.move(y, x + pos)
                window.refresh()
                key = self._uiDriver.get_key(window)
                gDebug(2, "KEY=%r" % key)
                if isinstance(key, basestring):
                    if key == chr(27):
                        result = ''
                        break
                    elif key == chr(10):
                        break
                    else:
                        if len(result) < maxlen:
                            result += key
                            pos += 1
                elif key == curses.KEY_BACKSPACE:
                    if len(result) > 0:
                        result = result[:-1]
                        pos -= 1

        finally:
            curses.curs_set(old)

        return result



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

configuration = {
  'baseClass': UIForm,
  'provides' : 'GFForm',
  'container': 1,
}
