#!/usr/bin/python
import codecs
import copy
import sys
import curses
import curses.ascii
#import curses.wrapper
import weakref
from . import npysGlobalOptions as GlobalOptions
from . import wgwidget_proto
import locale
import warnings

from .globals import DEBUG

# experimental
from .eveventhandler import EventHandler



EXITED_DOWN  =  1
EXITED_UP    = -1
EXITED_LEFT  = -2
EXITED_RIGHT =  2
EXITED_ESCAPE= 127
EXITED_MOUSE = 130

SETMAX       = 'SETMAX'
RAISEERROR   = 'RAISEERROR'

ALLOW_NEW_INPUT = True

TEST_SETTINGS = {
    'TEST_INPUT': None,
    'TEST_INPUT_LOG': [],
    'CONTINUE_AFTER_TEST_INPUT': False,
    'INPUT_GENERATOR': None,
    }


def add_test_input_from_iterable(test_input):
    global TEST_SETTINGS
    if not TEST_SETTINGS['TEST_INPUT']:
        TEST_SETTINGS['TEST_INPUT'] = []
    TEST_SETTINGS['TEST_INPUT'].extend([ch for ch in test_input])
    
def add_test_input_ch(test_input):
    global TEST_SETTINGS
    if not TEST_SETTINGS['TEST_INPUT']:
        TEST_SETTINGS['TEST_INPUT'] = []
    TEST_SETTINGS['TEST_INPUT'].append(test_input)
    

class ExhaustedTestInput(Exception):
    pass

class NotEnoughSpaceForWidget(Exception):
    pass

class InputHandler(object):
    "An object that can handle user input"

    def handle_input(self, _input):
        """Returns True if input has been dealt with, and no further action needs taking.
        First attempts to look up a method in self.input_handers (which is a dictionary), then
        runs the methods in self.complex_handlers (if any), which is an array of form (test_func, dispatch_func).
        If test_func(input) returns true, then dispatch_func(input) is called. Check to see if parent can handle.
        No further action taken after that point."""
        
        if _input in self.handlers:
            self.handlers[_input](_input)
            return True
        
        try:
            _unctrl_input = curses.ascii.unctrl(_input)
        except TypeError:
            _unctrl_input = None
        
        if _unctrl_input and (_unctrl_input in self.handlers):
            self.handlers[_unctrl_input](_input)
            return True


        if not hasattr(self, 'complex_handlers'): 
            return False
        else:
            for test, handler in self.complex_handlers:
                if test(_input) is not False: 
                    handler(_input)
                    return True
        if hasattr(self, 'parent_widget') and hasattr(self.parent_widget, 'handle_input'):
            if self.parent_widget.handle_input(_input):
                return True
        elif hasattr(self, 'parent') and hasattr(self.parent, 'handle_input'):
            if self.parent.handle_input(_input):
                return True

        else:
            pass
    # If we've got here, all else has failed, so:
        return False

    def set_up_handlers(self):
        """This function should be called somewhere during object initialisation (which all library-defined widgets do). You might like to override this in your own definition,
but in most cases the add_handers or add_complex_handlers methods are what you want."""
        #called in __init__
        self.handlers = {
                   curses.ascii.NL:     self.h_exit_down,
                   curses.ascii.CR:     self.h_exit_down,
                   curses.ascii.TAB:    self.h_exit_down,
                   curses.KEY_BTAB:     self.h_exit_up,
                   curses.KEY_DOWN:     self.h_exit_down,
                   curses.KEY_UP:       self.h_exit_up,
                   curses.KEY_LEFT:     self.h_exit_left,
                   curses.KEY_RIGHT:    self.h_exit_right,
                   # "^P":                self.h_exit_up,
                   # "^N":                self.h_exit_down,
                   curses.ascii.ESC:    self.h_exit_escape,
                   curses.KEY_MOUSE:    self.h_exit_mouse,
                   }

        self.complex_handlers = []

    def add_handlers(self, handler_dictionary):
        """Update the dictionary of simple handlers.  Pass in a dictionary with keyname (eg "^P" or curses.KEY_DOWN) as the key, and the function that key should call as the values """
        self.handlers.update(handler_dictionary)
    
    def add_complex_handlers(self, handlers_list):
        """add complex handlers: format of the list is pairs of
        (test_function, callback) sets"""

        for pair in handlers_list:
            assert len(pair) == 2
        self.complex_handlers.extend(handlers_list)
        
    def remove_complex_handler(self, test_function):
        _new_list = []
        for pair in self.complex_handlers:
            if not pair[0] == test_function:
                _new_list.append(pair)
        self.complex_handlers = _new_list

###########################################################################################
# Handler Methods here - npc convention - prefix with h_

    def h_exit_down(self, _input):
        """Called when user leaves the widget to the next widget"""
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_DOWN
        
    def h_exit_right(self, _input):
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_RIGHT

    def h_exit_up(self, _input):
        if not self._test_safe_to_exit():
            return False
        """Called when the user leaves the widget to the previous widget"""
        self.editing = False
        self.how_exited = EXITED_UP
        
    def h_exit_left(self, _input):
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_LEFT
        
    def h_exit_escape(self, _input):
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_ESCAPE

    def h_exit_mouse(self, _input):
        mouse_event = self.parent.safe_get_mouse_event()
        if mouse_event and self.intersted_in_mouse_event(mouse_event):
            self.handle_mouse_event(mouse_event)
        else:
            if mouse_event and self._test_safe_to_exit():
                curses.ungetmouse(*mouse_event)
                ch = self.parent.curses_pad.getch()
                assert ch == curses.KEY_MOUSE
            self.editing = False
            self.how_exited = EXITED_MOUSE
    
    



class Widget(InputHandler, wgwidget_proto._LinePrinter, EventHandler):
    "A base class for widgets. Do not use directly"
    
    _SAFE_STRING_STRIPS_NL = True
    
    def destroy(self):
        """Destroy the widget: methods should provide a mechanism to destroy any references that might
        case a memory leak.  See select. module for an example"""
        pass
        
    def __init__(self, screen, 
            relx=0, rely=0, name=None, value=None, 
            width = False, height = False,
            max_height = False, max_width=False,
            editable=True,
            hidden=False,
            color='DEFAULT',
            use_max_space=False,
            check_value_change=True,
            check_cursor_move=True,
            value_changed_callback=None,
            **keywords):
        """The following named arguments may be supplied:
        name= set the name of the widget.
        width= set the width of the widget.
        height= set the height.
        max_height= let the widget choose a height up to this maximum.
        max_width=  let the widget choose a width up to this maximum.
        editable=True/False the user may change the value of the widget.
        hidden=True/False The widget is hidden.
        check_value_change=True - perform a check on every keypress and run when_value_edit if the value is different.
        check_cursor_move=True - perform a check every keypress and run when_cursor_moved if the cursor has moved.
        value_changed_callback - should be None or a Function.  If it is a function, it will have be called when the value changes
                               and passed the keyword argument widget=self.
        """
        self.check_value_change=check_value_change
        self.check_cursor_move =check_cursor_move
        self.hidden = hidden
        self.interested_in_mouse_even_when_not_editable = False# used only for rare widgets to allow user to click
                                                        # even if can't actually select the widget.  See mutt-style forms
                                                        
        try:
            self.parent = weakref.proxy(screen)
        except TypeError:
            self.parent = screen
        self.use_max_space = use_max_space    
        self.set_relyx(rely, relx)
        #self.relx = relx
        #self.rely = rely
        self.color = color
        self.encoding = 'utf-8'#locale.getpreferredencoding()
        if GlobalOptions.ASCII_ONLY or locale.getpreferredencoding() == 'US-ASCII':
            self._force_ascii = True
        else:
            self._force_ascii = False
        
        
        self.set_up_handlers()
        
        # To allow derived classes to set this and then call this method safely...
        try:
            self.value
        except AttributeError:
            self.value = value

        # same again
        try:
            self.name
        except:
            self.name=name
            
        self.request_width =  width     # widgets should honour if possible
        self.request_height = height    # widgets should honour if possible

        self.max_height = max_height
        self.max_width = max_width

        self.set_size()

        self.editing = False        # Change to true during an edit
        
        self.editable = editable
        if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True
        else: self.on_last_line = False
        
        if value_changed_callback:
            self.value_changed_callback = value_changed_callback
        else:
            self.value_changed_callback = None
        
        self.initialize_event_handling()
    
    def set_relyx(self, y, x):
        """
        Set the position of the widget on the Form.  If y or x is a negative value,
        npyscreen will try to position it relative to the bottom or right edge of the 
        Form.  Note that this ignores any margins that the Form may have defined.
        This is currently an experimental feature.  A future version of the API may 
        take account of the margins set by the parent Form.
        """
        self._requested_rely = y
        self._requested_relx = x
        if y >= 0:
            self.rely = y
        else:
            self._requested_rely = y
            self.rely = self.parent.curses_pad.getmaxyx()[0] + y
            # I don't think there is any real value in using these margins
            #if self.parent.BLANK_LINES_BASE and not self.use_max_space:
            #    self.rely -= self.parent.BLANK_LINES_BASE
            if self.rely < 0:
                self.rely = 0
        if x >= 0:
            self.relx = x            
        else:
            self.relx = self.parent.curses_pad.getmaxyx()[1] + x
            # I don't think there is any real value in using these margins
            #if self.parent.BLANK_COLUMNS_RIGHT and not self.use_max_space:
            #    self.relx -= self.parent.BLANK_COLUMNS_RIGHT
            if self.relx < 0:
                self.relx = 0
    
    def _move_widget_on_terminal_resize(self):
        if self._requested_rely < 0 or self._requested_relx < 0:
            self.set_relyx(self._requested_rely, self._requested_relx)
    
    def _resize(self):
        "Internal Method. This will be the method called when the terminal resizes."
        self._move_widget_on_terminal_resize()
        self._recalculate_size()
        if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True
        else: self.on_last_line = False
        self.resize()
        self.when_resized()
    
    def resize(self):
        "Widgets should override this to control what should happen when they are resized."
        pass
        
    def _recalculate_size(self):
        return self.set_size()
    
    def when_resized(self):
        # this method is called when the widget has been resized.
        pass
    
        
    def do_colors(self):
        "Returns True if the widget should try to paint in coloour."
        if curses.has_colors() and not GlobalOptions.DISABLE_ALL_COLORS:
            return True
        else:
            return False
        
    def space_available(self):
        """The space available left on the screen, returned as rows, columns"""
        if self.use_max_space:
            y, x = self.parent.useable_space(self.rely, self.relx)
        else:
            y, x = self.parent.widget_useable_space(self.rely, self.relx)
        return y,x

    def calculate_area_needed(self): 
        """Classes should provide a function to
calculate the screen area they need, returning either y,x, or 0,0 if
they want all the screen they can.  However, do not use this to say how
big a given widget is ... use .height and .width instead"""
        return 0,0

    def set_size(self):
        """Set the size of the object, reconciling the user's request with the space available"""
        my, mx = self.space_available()
        #my = my+1 # Probably want to remove this.
        ny, nx = self.calculate_area_needed()
        
        max_height = self.max_height
        max_width  = self.max_width
        # What to do if max_height or max_width is negative
        if max_height not in (None, False) and max_height < 0:
            max_height = my + max_height
        if max_width not in (None, False) and max_width < 0:
            max_width = mx + max_width
            
        if max_height not in (None, False) and max_height <= 0:
            raise NotEnoughSpaceForWidget("Not enough space for requested size")  
        if max_width not in (None, False) and max_width <= 0:
            raise NotEnoughSpaceForWidget("Not enough space for requested size")
        
        if ny > 0:
            if my >= ny: 
                self.height = ny
            else: 
                self.height = RAISEERROR
        elif max_height:
            if max_height <= my: 
                self.height = max_height
            else: 
                self.height = self.request_height
        else: 
            self.height = (self.request_height or my)
        
        #if mx <= 0 or my <= 0:
        #    raise Exception("Not enough space for widget")


        if nx > 0:                 # if a minimum space is specified....
            if mx >= nx:           # if max width is greater than needed space 
                self.width = nx    # width is needed space
            else: 
                self.width = RAISEERROR    # else raise an error
        elif self.max_width:       # otherwise if a max width is speciied
            if max_width <= mx: 
                self.width = max_width
            else: 
                self.width = RAISEERROR
        else: 
            self.width = self.request_width or mx

        if self.height == RAISEERROR or self.width == RAISEERROR:
            # Not enough space for widget
            raise NotEnoughSpaceForWidget("Not enough space: max y and x = %s , %s. Height and Width = %s , %s " % (my, mx, self.height, self.width) ) # unsafe. Need to add error here.
    
    def update(self, clear=True):
        """How should object display itself on the screen. Define here, but do not actually refresh the curses
        display, since this should be done as little as possible.  This base widget puts nothing on screen."""
        if self.hidden:
            self.clear()
            return True

    def display(self):
        """Do an update of the object AND refresh the screen"""
        if self.hidden:
            self.clear()
            self.parent.refresh()
        else:
            self.update()
            self.parent.refresh()

    def set_editable(self, value):
        if value: self._is_editable = True
        else: self._is_editable = False

    def get_editable(self):
        return(self._is_editable)

    def clear(self, usechar=' '):
        """Blank the screen area used by this widget, ready for redrawing"""
        for y in range(self.height):
#This method is too slow
#           for x in range(self.width+1):
#               try:
#                   # We are in a try loop in case the cursor is moved off the bottom right corner of the screen
#                   self.parent.curses_pad.addch(self.rely+y, self.relx + x, usechar)
#               except: pass
#Use this instead
            if self.do_colors():
                self.parent.curses_pad.addstr(self.rely+y, self.relx, usechar * (self.width), self.parent.theme_manager.findPair(self, self.parent.color))  # used to be width + 1
            else:
                self.parent.curses_pad.addstr(self.rely+y, self.relx, usechar * (self.width))  # used to be width + 1

    def edit(self):
        """Allow the user to edit the widget: ie. start handling keypresses."""
        self.editing = 1
        self._pre_edit()
        self._edit_loop()
        return self._post_edit()

    def _pre_edit(self):
        self.highlight = 1
        old_value = self.value
        self.how_exited = False

    def _edit_loop(self):
        if not self.parent.editing:
            _i_set_parent_editing = True
            self.parent.editing   = True
        else:
            _i_set_parent_editing = False
        while self.editing and self.parent.editing:
            self.display()
            self.get_and_use_key_press()
        if _i_set_parent_editing:
            self.parent.editing = False
        
        if self.editing:
            self.editing    = False
            self.how_exited = True

    def _post_edit(self):
        self.highlight = 0
        self.update()
        
    def _get_ch(self):
        #try:
        #    # Python3.3 and above - returns unicode
        #    ch = self.parent.curses_pad.get_wch()
        #    self._last_get_ch_was_unicode = True
        #except AttributeError:
            
        # For now, disable all attempt to use get_wch()
        # but everything that follows could be in the except clause above.
        
            # Try to read utf-8 if possible.
        _stored_bytes =[]
        self._last_get_ch_was_unicode = True
        global ALLOW_NEW_INPUT
        if ALLOW_NEW_INPUT == True and locale.getpreferredencoding() == 'UTF-8':
            ch = self.parent.curses_pad.getch()
            if ch <= 127:
                rtn_ch = ch
                self._last_get_ch_was_unicode = False
                return rtn_ch
            elif ch <= 193:
                rtn_ch = ch
                self._last_get_ch_was_unicode = False
                return rtn_ch
            # if we are here, we need to read 1, 2 or 3 more bytes.
            # all of the subsequent bytes should be in the range 128 - 191, 
            # but we'll risk not checking...
            elif 194 <= ch <= 223: 
                    # 2 bytes
                    _stored_bytes.append(ch)
                    _stored_bytes.append(self.parent.curses_pad.getch())
            elif 224 <= ch <= 239: 
                    # 3 bytes 
                    _stored_bytes.append(ch)
                    _stored_bytes.append(self.parent.curses_pad.getch()) 
                    _stored_bytes.append(self.parent.curses_pad.getch()) 
            elif 240 <= ch <= 244: 
                    # 4 bytes 
                    _stored_bytes.append(ch) 
                    _stored_bytes.append(self.parent.curses_pad.getch()) 
                    _stored_bytes.append(self.parent.curses_pad.getch()) 
                    _stored_bytes.append(self.parent.curses_pad.getch())
            elif ch >= 245:
                # probably a control character
                self._last_get_ch_was_unicode = False
                return ch
            
            if sys.version_info[0] >= 3:
                ch = bytes(_stored_bytes).decode('utf-8', errors='strict')
            else:
                ch = ''.join([chr(b) for b in _stored_bytes])
                ch = ch.decode('utf-8')
        else:
            ch = self.parent.curses_pad.getch()
            self._last_get_ch_was_unicode = False
        
        # This line should not be in the except clause.
        return ch

    def try_adjust_widgets(self):
        if hasattr(self.parent, "adjust_widgets"):
            self.parent.adjust_widgets()
        if hasattr(self.parent, "parentApp"):
            if hasattr(self.parent.parentApp, "_internal_adjust_widgets"):
                self.parent.parentApp._internal_adjust_widgets()
            if hasattr(self.parent.parentApp, "adjust_widgets"):
                self.parent.parentApp.adjust_widgets()
            
    
    def try_while_waiting(self):
        if hasattr(self.parent, "while_waiting"):
            self.parent.while_waiting()
        if hasattr(self.parent, "parentApp"):
            if hasattr(self.parent.parentApp, "_internal_while_waiting"):
                self.parent.parentApp._internal_while_waiting()
            if hasattr(self.parent.parentApp, "while_waiting"):
                self.parent.parentApp.while_waiting()

    def get_and_use_key_press(self):
        global TEST_SETTINGS
        if (TEST_SETTINGS['TEST_INPUT'] is None) and (TEST_SETTINGS['INPUT_GENERATOR'] is None):
            curses.raw()
            curses.cbreak()
            curses.meta(1)
            self.parent.curses_pad.keypad(1)
            if self.parent.keypress_timeout:
                curses.halfdelay(self.parent.keypress_timeout)
                ch = self._get_ch()
                if ch == -1:
                    return self.try_while_waiting()
            else:
                self.parent.curses_pad.timeout(-1)
                ch = self._get_ch()
            # handle escape-prefixed rubbish.
            if ch == curses.ascii.ESC:
                #self.parent.curses_pad.timeout(1)
                self.parent.curses_pad.nodelay(1)
                ch2 = self.parent.curses_pad.getch()
                if ch2 != -1: 
                    ch = curses.ascii.alt(ch2)
                self.parent.curses_pad.timeout(-1) # back to blocking mode
                #curses.flushinp()
        elif (TEST_SETTINGS['INPUT_GENERATOR']):
            self._last_get_ch_was_unicode = True
            try:
                ch = next(TEST_SETTINGS['INPUT_GENERATOR'])
            except StopIteration:
                if TEST_SETTINGS['CONTINUE_AFTER_TEST_INPUT']:
                    TEST_SETTINGS['INPUT_GENERATOR'] = None
                    return
                else:
                    raise ExhaustedTestInput
        else:
            self._last_get_ch_was_unicode = True
            try:
                ch = TEST_SETTINGS['TEST_INPUT'].pop(0)
                TEST_SETTINGS['TEST_INPUT_LOG'].append(ch)
            except IndexError:
                if TEST_SETTINGS['CONTINUE_AFTER_TEST_INPUT']:
                    TEST_SETTINGS['TEST_INPUT'] = None
                    return
                else:
                    raise ExhaustedTestInput
        
        self.handle_input(ch)
        if self.check_value_change:
            self.when_check_value_changed()
        if self.check_cursor_move:
            self.when_check_cursor_moved()
        
        
        self.try_adjust_widgets()
            
    def intersted_in_mouse_event(self, mouse_event):
        if not self.editable and not self.interested_in_mouse_even_when_not_editable:
            return False
        mouse_id, x, y, z, bstate = mouse_event
        x += self.parent.show_from_x
        y += self.parent.show_from_y
        if self.relx <= x <= self.relx + self.width-1 + self.parent.show_atx:
            if self.rely  <= y <= self.rely + self.height-1 + self.parent.show_aty:
                return True
        return False
    
    def handle_mouse_event(self, mouse_event):
        # mouse_id, x, y, z, bstate = mouse_event
        pass
    
    def interpret_mouse_event(self, mouse_event):
        mouse_id, x, y, z, bstate = mouse_event
        x += self.parent.show_from_x
        y += self.parent.show_from_y
        rel_y       = y - self.rely - self.parent.show_aty
        rel_x = x - self.relx - self.parent.show_atx
        return (mouse_id, rel_x, rel_y, z, bstate)
        
    #def when_parent_changes_value(self):
        # Can be called by forms when they chage their value.
        #pass

    def _safe_to_exit(self):
        return True
        
    def safe_to_exit(self):
        return True
    
    def _test_safe_to_exit(self):
        if self._safe_to_exit() and self.safe_to_exit():
            return True
        else:
            return False
    

    def when_check_value_changed(self):
        "Check whether the widget's value has changed and call when_valued_edited if so."
        try:
            if self.value == self._old_value:
                return False
        except AttributeError:
            self._old_value = copy.deepcopy(self.value)
            self.when_value_edited()
        # Value must have changed:
        self._old_value = copy.deepcopy(self.value)
        self._internal_when_value_edited()
        self.when_value_edited()
        if hasattr(self, 'parent_widget'):
            self.parent_widget.when_value_edited()
            self.parent_widget._internal_when_value_edited()
        return True
    
    def _internal_when_value_edited(self):
        if self.value_changed_callback:
            return self.value_changed_callback(widget=self)
    
    def when_value_edited(self):
        """Called when the user edits the value of the widget.  Will usually also be called the first time
        that the user edits the widget."""
        pass
    
    def when_check_cursor_moved(self):
        if hasattr(self, 'cursor_line'):
            cursor = self.cursor_line
        elif hasattr(self, 'cursor_position'):
            cursor = self.cursor_position
        elif hasattr(self, 'edit_cell'):
            cursor = copy.copy(self.edit_cell)
        else:
            return None
        try:
            if self._old_cursor == cursor:
                return False
        except AttributeError:
            pass
        # Value must have changed:
        self._old_cursor = cursor
        self.when_cursor_moved()
        if hasattr(self, 'parent_widget'):
            self.parent_widget.when_cursor_moved()
        
    def when_cursor_moved(self):
        "Called when the cursor moves"
        pass

    def safe_string(self, this_string):
        """Check that what you are trying to display contains only
        printable chars.  (Try to catch dodgy input).  Give it a string,
        and it will return a string safe to print - without touching
        the original.  In Python 3 this function is not needed
        
        N.B. This will return a unicode string on python 3 and a utf-8 string
        on python2
        """
        try:
            if not this_string: 
                return ""
            #this_string = str(this_string)
            # In python 3
            #if sys.version_info[0] >= 3:
            #    return this_string.replace('\n', ' ')
            if self.__class__._SAFE_STRING_STRIPS_NL == True:
                rtn_value = this_string.replace('\n', ' ')
            else:
                rtn_value = this_string
        
            # Does the terminal want ascii?
            if self._force_ascii:
                if isinstance(rtn_value, bytes):
                    # no it isn't.
                    try:
                        rtn_value = rtn_value.decode(self.encoding, 'replace')
                    except TypeError:
                        # Python2.6
                        rtn_value = rtn_value.decode(self.encoding, 'replace')
                else:
                    if sys.version_info[0] >= 3:
                        # even on python3, in this case, we want a string that
                        # contains only ascii chars - but in unicode, so:
                        rtn_value = rtn_value.encode('ascii', 'replace').decode()
                        return rtn_value     
                    else:
                        return rtn_value.encode('ascii', 'replace')
                return rtn_value
            # If not....
            if not GlobalOptions.ASCII_ONLY:
                # is the string already unicode?
                if isinstance(rtn_value, bytes):
                    # no it isn't.
                    try:
                        rtn_value = rtn_value.decode(self.encoding, 'replace')
                    except:
                        # Python2.6
                        rtn_value = rtn_value.decode(self.encoding, 'replace')
                if sys.version_info[0] >= 3:
                    return rtn_value     
                else:
                    return rtn_value.encode('utf-8', 'replace')
            else:
                rtn = self.safe_filter(this_string)
                return rtn
        except:
            if DEBUG:
                raise
            else:
                return "*ERROR DISPLAYING STRING*"
    
    def safe_filter(self, this_string):
        try:
            this_string = this_string.decode(self.encoding, 'replace')
            return this_string.encode('ascii', 'replace').decode()
        except:
            # Things have gone badly wrong if we get here, but let's try to salvage it.
            try:
                if self._safe_filter_value_cache[0] == this_string:
                    return self._safe_filter_value_cache[1]
            except AttributeError:
                pass
            s = []
            for cha in this_string.replace('\n', ' '):
                #if curses.ascii.isprint(cha):
                #    s.append(cha)
                #else:
                #    s.append('?')
                try:
                    s.append(str(cha))
                except:
                    s.append('?')
            s = ''.join(s)
        
            self._safe_filter_value_cache = (this_string, s)
        
            return s
        
        #s = ''
        #for cha in this_string.replace('\n', ''):
        #    try:
        #        s += cha.encode('ascii')
        #    except:
        #        s += '?'
        #return s
        
class DummyWidget(Widget):
    "This widget is invisible and does nothing.  Which is sometimes important."
    def __init__(self, screen, *args, **keywords):
        super(DummyWidget, self).__init__(screen, *args, **keywords)
        self.height = 0
        self.widget = 0
        self.parent = screen
    def display(self):
        pass
    def update(self, clear=False):
        pass
    def set_editable(self, value):
        if value: self._is_editable = True
        else: self._is_editable = False
    def get_editable(self):
        return(self._is_editable)
    def clear(self, usechar=' '):
        pass
    def calculate_area_needed(self):
        return 0,0