# Copyright (c) 2000-2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# 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.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Preferences Model"""

__revision__ = "$Id: PrefModel.py,v 1.20 2002/01/17 18:41:21 zoe Exp $"

from os import path
from narval.utils.dp import Observable
from narval.utils import misc
from narval import config
from narval.utils.sax import DictSaxHandlerMixin, PrefReader

class PrefModel(Observable):
    """ model of user preferences using singleton pattern """
    _FILE = path.normpath(path.join(config.get_home(),
                                    'data/horn_preferences.xml'))
    _DICT_ = {}
    
    def __init__(self):
        self.__dict__ = self._DICT_
        if not self.__dict__ :
            Observable.__init__(self)
            self.reader = PrefReader(PrefSaxHandler, self)
            # pref structures
            self._prefs = {'recipe': {'step':{}, 'transition':{}},
                           'monitoring': {},
                           'messages': {}}
            # shortcuts
            self.messages_style = self._prefs['messages']
            self.monitoring = self._prefs['monitoring']
            self.steps_style = self._prefs['recipe']['step']
            self.trans_style = self._prefs['recipe']['transition']
            # state
            self.current_style = None
            # parse default preferences to initialise pref structures
            self.reader.fromString(DEFAULT_PREF)
            try:
                self.reader.fromFile(self._FILE)
            except:
                print 'Error while loading preferences file:'
                import traceback
                traceback.print_exc()
            self._build_style_list()
            
    def __getitem__(self, key):
        return self._prefs[key]

    def __setitem__(self, key, val):
        self._prefs[key] = val

    def add_changes(self, key, new_value):
        """ add a change to "changes" attribute"""
        count = 0
        for ( a , b ) in self.changes:
            if a == key:
                self.changes[count] = (key, new_value)
                break
            count = count+1
        

    def keys(self):
        return self._prefs.keys()
    
    def setdefault(self, key, val):
        return self._prefs.setdefault(key, val)
    
    def set_pushed_color(self, color):
        self.messages_style['pushed']['gtkcolor'] = color
        self.changes.append(("pushed-color",))

    def set_popped_color(self, color):
        self.messages_style['popped']['gtkcolor'] = color
        self.changes.append(("popped-color",))
        
    def set_font(self, font, type, state):
        from gtk import load_font
        self.current_style['font'] = font
        self.current_style['gtkstyle'].font = load_font(font)
        self.update_sample_from_change(type, state)
        
    def set_destroy(self,*args):
        self.monitoring['destroy'] = args[-1]
        self.add_changes(('destroy',args[-1]))

    def set_maxchars(self, *args):
        self._prefs['recipe']['max-display-char'] = args[-1]


    ## update element on recipe ###############################################
    def get_style_from_state(self, type, state):
        assert type in ('step', 'transition')
        self._prefs['recipe'][type][state]

    ## Preferences file functions #############################################
    def save_preferences(self):
        import narval.gui
        file = open(self._FILE, 'w')
        self._update_style_list()
        self._style_list_as_xml(file, narval.gui.ENCODING)
        file.close()

    ## Update sample functions ################################################
    def update_sample_from_menu(self, type, state):
        # just an update from model to view for the sample
        self.current_style = self._prefs['recipe'][type][state]
        from PrefView import PrefView
        PrefView().update_sample(self)

    def update_sample_from_change(self, type, state):
        self._prefs['recipe'][type][state] = self.current_style
        from PrefView import PrefView
        PrefView().update_sample(self)

    ## Private ################################################################
    def _build_style_list(self):
        from gtk import GtkLabel
        tmp = GtkLabel("")
        style = tmp.get_style().copy()
        cmap = tmp.get_colormap()
        for struct in self.steps_style.values():
            struct['gtkstyle'] = _gtkstyle_from_struct(struct, cmap, style.copy())
        for struct in self.trans_style.values():
            struct['gtkstyle'] = _gtkstyle_from_struct(struct, cmap, style.copy())
        for style in self.messages_style.values():
            style['gtkcolor'] = cmap.alloc(*style['color'])
        
        self.changes.append((self.monitoring['destroy'], style))
        self.notify_observers()

    def _update_style_list(self):
        for struct in self.steps_style.values():
            _struct_from_gtkstyle(struct)
        for struct in self.trans_style.values():
            _struct_from_gtkstyle(struct)
        for style in self.messages_style.values():
            style['color'] = gtkcolor_to_rgb(style['gtkcolor'])
        
    def _style_list_as_xml(self, buf, encoding='UTF-8'):
        buf.write('''<?xml version="1.0" encoding="%s" standalone="no" ?>
<!DOCTYPE preferences PUBLIC  "-//Logilab//DTD Horn preferences//EN" 
"horn_preferences.dtd" []>\n\n<preferences>
<part name="recipe">
  <max-display-char>%s</max-display-char>
  <entry name="step">\n''' % (encoding,
                              self._prefs['recipe']['max-display-char']))
        for state, struct in self.steps_style.items():
            _struct_to_xml(struct, state, buf, encoding)
        buf.write('  </entry><entry name="transition">')
        for state, struct in self.trans_style.items():
            _struct_to_xml(struct, state, buf, encoding)
        c1 = self.messages_style['popped']['color']
        c2 = self.messages_style['pushed']['color']
        buf.write('''  </entry>\n</part>\n\n<part name="messages">
   <popped>
      <color>%d %d %d</color>
    </popped>
    <pushed>
      <color>%d %d %d</color>
    </pushed>
</part>\n\n<part name="monitoring">
  <destroy>%s</destroy>
</part>\n\n</preferences>'''% (c1[0], c1[1], c1[2], c2[0], c2[1], c2[2],
                             misc.rev_value(self.monitoring['destroy'])))
        
    
## Utils ######################################################################
def _struct_to_xml(struct, state, buf, encoding):
    bg, fg = struct['background'], struct['foreground']
    b = struct['border']
    c = b['color']
    buf.write('''    <state name="%s">
      <background>%d %d %d</background>
      <foreground>%d %d %d</foreground>
      <font>%s</font>
      <border>
        <width>%d</width>
        <color>%d %d %d</color>
      </border>
    </state>\n''' % (state, bg[0], bg[1], bg[2], fg[0], fg[1], fg[2],
                     struct['font'], b['width'], c[0], c[1], c[2]))

def _struct_from_gtkstyle(struct):
    import sys
    if sys.platform=='win32':
        from GTKconst import STATE_NORMAL
    else:
        from GTK import STATE_NORMAL
    gtks = struct['gtkstyle']
    struct['background'] = gtkcolor_to_rgb(gtks.bg[STATE_NORMAL])
    struct['foreground'] = gtkcolor_to_rgb(gtks.fg[STATE_NORMAL])
    struct['border']['color'] = gtkcolor_to_rgb(gtks.dark[STATE_NORMAL])

def _gtkstyle_from_struct(struct, colormap, style):
    # load values
    from gtk import load_font
    style.font = load_font(struct['font'])
    bg = struct['background']
    fg = struct['foreground']
    bord_col = struct['border']['color']
    for i in range(5):
        style.fg[i] = colormap.alloc(*fg)
        style.bg[i] = colormap.alloc(*bg)
        style.dark[i] = colormap.alloc(*bord_col)
        style.light[i] = colormap.alloc(*bord_col)
        style.base[i] = colormap.alloc(*bord_col)
    return style

def text_to_rgb(str):
    (r,g,b) = str.split()
    return (int(r), int(g), int(b))

def gtkcolor_to_rgb(col):
    return (col.red, col.green, col.blue)

## saxHandler definition ######################################################
class PrefSaxHandler(DictSaxHandlerMixin):
    _MASTER = {'part':'name', 'entry':'name', 'state':'name', 'border':None, 
               'popped':None, 'pushed':None}
    _S_LIST, _LIST = (), ()
    _KEY = ('background', 'foreground', 'font', 'color', 'width', 'destroy',
            'max-display-char')
    _CB = {'background': text_to_rgb, 'foreground': text_to_rgb,
           'color': text_to_rgb, 'width': int,
           'destroy': misc.value, 'max-display-char': int}

## Default preferences ########################################################
DEFAULT_PREF = '''<?xml version="1.0" encoding="iso-8859-1" standalone="no" ?>
<!DOCTYPE preferences PUBLIC  "-//Logilab//DTD Horn preferences//EN" "horn_preferences.dtd" []>
<preferences>
<part name='recipe'>
  <max-display-char>20</max-display-char>
  <entry name='step'>
    <state name='ready'>
      <background>13909 725 37732</background>
      <foreground>64963 64418 984</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>10853 4393 22526</color>
	<width>0</width>
      </border>
    </state><state name='end'>
      <background>0 725 37732</background>
      <foreground>64963 64639 63591</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>3961 4317 22450</color>
	<width>0</width>
      </border>
    </state><state name='todo'>
      <background>27467 27694 54116</background>
      <foreground>65535 62119 311</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>17790 17973 39317</color>
	<width>0</width>
      </border>
    </state><state name='running'>
      <background>45924 51953 62059</background>
      <foreground>496 94 109</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>24222 34364 51365</color>
	<width>0</width>
      </border>
    </state><state name='history'>
      <background>52279 64542 55244</background>
      <foreground>31774 31774 31774</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>26038 55735 33219</color>
	<width>0</width>
      </border>
    </state><state name='done'>
      <background>52681 65038 55669</background>
      <foreground>59080 13901 13901</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
	<width>0</width>
        <color>25436 56966 33060</color>
      </border>
    </state><state name='error'>
      <background>63052 8322 7409</background>
      <foreground>65535 65535 65535</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>40509 9332 8812</color>
	<width>1</width>
      </border>
    </state><state name='failed'>
      <background>62556 23534 8853</background>
      <foreground>65535 65535 65535</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>40741 17855 9245</color>
	<width>1</width>
      </border>
    </state><state name='impossible'>
      <background>65535 64151 65208</background>
      <foreground>34433 34264 36739</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>59491 31288 52827</color>
	<width>1</width>
      </border>
    </state>
  </entry><entry name='transition'>
    <state name='impossible'>
      <background>64542 64542 64542</background>
      <foreground>39221 39221 39221</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>45179 45179 45179</color>
	<width>0</width>
      </border>
    </state><state name='failed'>
      <background>58584 36439 11716</background>
      <foreground>496 496 496</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>37888 25335 11321</color>
	<width>0</width>
      </border>
    </state><state name='fireable'>
      <background>48374 64045 47655</background>
      <foreground>60570 12251 16640</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>24808 54755 23434</color>
	<width>1</width>
      </border>
    </state><state name='fired'>
      <background>48374 64045 47655</background>
      <foreground>29481 29787 30781</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>24808 54755 23434</color>
	<width>1</width>
      </border>
    </state><state name='wait-step'>
      <background>65139 62258 65535</background>
      <foreground>0 0 0</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>55772 30162 59292</color>
	<width>0</width>
      </border>
    </state><state name='wait-condition'>
      <background>64542 54178 60134</background>
      <foreground>0 0 0</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>55640 27463 43656</color>
	<width>0</width>
      </border>
    </state><state name='wait-time'>
      <background>63052 58658 44413</background>
      <foreground>5691 7447 7339</foreground>
      <font>-adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-1</font>
      <border>
        <color>53046 45769 22179</color>
	<width>0</width>
      </border>
    </state>
  </entry>
</part><part name='messages'>
   <popped>
      <color>65000 54994 57859</color>
    </popped>
    <pushed>
      <color>54803 65000 54053</color>
    </pushed>
</part><part name='monitoring'>
  <destroy>no</destroy>
</part>
</preferences>
'''

