#!/usr/bin/env python
# -*- coding: latin-1 -*-

# (c) Olivier Pirson --- DragonSoft
# http://www.opimedia.be/DS/
# Débuté le 10 février 2010
# v.01.00.00 --- 14 février 2010
# v.01.01.00 --- 15 mars 2010 : remplacé les '...' % (...) par '...'.format(...)
#            --- 2 janvier 2012 : nouveau site web
#################################################################################
VERSION = 'v.01.01.00 --- 2012 January 2 '

if __debug__:
    import sys

    assert sys.version_info[0] >= 3, ("Require Python 3 or better", sys.version)


import collections
import os, os.path

import tkinter as tk
import tkinter.filedialog, tkinter.messagebox
import tkinter.tix



#######################
# Constantes globales #
#######################
# Tuple des String des choix pour l'option encoding de la fonction open()
ENCODING_STR = ('None',
                'ascii',
                'cp1250', 'cp1252',
                'latin_1', 'iso8859_2', 'iso8859_15',
                'mac_latin2', 'mac_roman',
                'utf_8', 'utf_16', 'utf_32')

# Tuple des choix pour l'option errors de la fonction open()
ERRORS = ('strict', 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace', 'surrogateescape',
          'surrogatepass')

# Tuple des choix pour l'option newline de la fonction open()
NEWLINE = (None, '',
           '\n', '\r', '\r\n')

# Tuple des String des choix pour l'option newline de la fonction open(mode='w')
NEWLINE_DEST_STR = ('None  (\\n ' + chr(8594) + ' os.linesep)', '  (\\n, \\r and \\r\\n)',
                    '\\n  (POSIX)', '\\r  (Mac)', '\\r\\n  (Windows)')

# Tuple des String des choix pour l'option newline de la fonction open(mode='r')
NEWLINE_SRC_STR = ('None  (\\n, \\r and \\r\\n ' + chr(8594) + ' \\n)', '  (\\n, \\r and \\r\\n)',
                   '\\n  (POSIX)', '\\r  (Mac)', '\\r\\n  (Windows)')



####################
# Variable globale #
####################
# Couple (Tuple contenant les lignes du fichier,
#         Tuple des fins de ligne rencontrées,
#         éventuelle erreur lors de sa lecture)
content_file = (tuple(), None, None)



#######################################
# Variables globales pour l'interface #
#######################################
# Tuple (après construction de la List) des widgets qui seront désactivés/activés
state_seq = []


# Fenêtre principale
tix_root = tk.tix.Tk()


# True si il faut afficher les numéros de ligne, False sinon
tix_Var_shownumline = tk.tix.IntVar()
tix_Var_shownumline.set(True)

# True si il faut montrer les fins de ligne, False sinon
tix_Var_showendofline = tk.tix.IntVar()
tix_Var_showendofline.set(True)


# Paramètre encoding du fichier destination
tix_Var_dest_encoding = tk.tix.StringVar()
tix_Var_dest_encoding.set('latin_1')

# Paramètre errors du fichier destination
tix_Var_dest_errors = tk.tix.StringVar()
tix_Var_dest_errors.set('strict')

# Nom (et chemin d'accès) du fichier destination
tix_Var_dest_filename = tk.tix.StringVar()

# Paramètre newline du fichier destination
tix_Var_dest_newline = tk.tix.StringVar()
tix_Var_dest_newline.set(NEWLINE_DEST_STR[0])


# Paramètre encoding du fichier source
tix_Var_src_encoding = tk.tix.StringVar()
tix_Var_src_encoding.set('latin_1')

# Paramètre errors du fichier source
tix_Var_src_errors = tk.tix.StringVar()
tix_Var_src_errors.set('strict')

# Nom (et chemin d'accès) du fichier source
tix_Var_src_filename = tk.tix.StringVar()

# Paramètre newline du fichier source
tix_Var_src_newline = tk.tix.StringVar()
tix_Var_src_newline.set(NEWLINE_SRC_STR[0])



#############
# Fonctions #
#############
def load_file(filename):
    """Ouvre le fichier filename
    et renvoie son contenu sous forme d'un couple
    (Tuple des lignes du fichier, Tuple des fins de ligne rencontrées, éventuel message d'erreur).

    Les paramètres encoding, errors et newline de la fonction open(mode='r')
    sont fixés par les variables tix_Var_src_encoding, tix_Var_src_errors et tix_Var_src_newline
    pre: filename: String
    result: (Tuple de String, None ou Tuple de String, None ou String)"""
    assert isinstance(filename, str), (type(filename), filename)

    encoding = tix_Var_src_encoding.get()
    if encoding == 'None':
        encoding = None

    errors = tix_Var_src_errors.get()
    if errors == 'None':
        errors = None

    newline = NEWLINE[NEWLINE_SRC_STR.index(tix_Var_src_newline.get())]

    # Ouverture
    try:
        f = open(filename, mode='r', encoding=encoding, errors=errors, newline=newline)
    except IOError:
        return (tuple(), None, "! Open file '{0}' error !\n".format(filename))

    # Lecture
    error = None
    try:
        seq = tuple(f.readlines())
    except:
        seq = tuple()
        error = '! Readlines error !\n'

    # Fermeture
    newlines = f.newlines
    if newlines != None:
        newlines = (newlines if isinstance(newlines, tuple)
                    else tuple(newlines))
    f.close()
    return (seq, newlines, error)


def save_file(filename, seq):
    """Sauve la séquence de String seq sous le nom filename
    et renvoie None ou un message d'erreur
    (si le fichier filename existe déjà il est écrasé)

    Les paramètres encoding, errors et newline de la fonction open(mode='w')
    sont fixés par les variables tix_Var_dest_encoding, tix_Var_dest_errors et tix_Var_dest_newline
    pre: filename: String
         seq: Sequence de String
    result: None ou String"""
    assert isinstance(filename, str), (type(filename), filename)
    assert isinstance(seq, collections.Sequence), type(seq)

    encoding = tix_Var_dest_encoding.get()
    if encoding == 'None':
        encoding = None

    errors = tix_Var_dest_errors.get()
    if errors == 'None':
        errors = None

    newline = NEWLINE[NEWLINE_DEST_STR.index(tix_Var_dest_newline.get())]

    # Ouverture
    try:
        f = open(filename, mode='w', encoding=encoding, errors=errors, newline=newline)
    except IOError:
        return "Open file '{0}' error!\n".format(filename)

    # Écriture
    error = None
    try:
        f.writelines(seq)
    except:
        error = 'Writelines error!\n'

    # Fermeture
    f.close()
    return error



##############################
# Fonctions pour l'interface #
##############################
def cmd_about():
    """MessageBox About..."""
    cmd_state_off()
    tix_Button_about.config(relief=tkinter.SUNKEN)
    tk.messagebox.showinfo('About' + chr(8230),
                           """transfileTk

{0}
http://www.opimedia.be/DS/

{1} Olivier Pirson {2} DragonSoft
olivier_pirson_opi@yahoo.fr""".format(VERSION.replace('---', chr(8212)), chr(169), chr(8212)))
    tix_Button_about.config(relief=tkinter.RAISED)
    cmd_state_on()


def cmd_change_dest_filename():
    """Sélecteur de fichiers pour spécifier le fichier destination"""
    cmd_state_off()
    tix_Var_dest_filename.set(tkinter.filedialog.asksaveasfilename(title='Destination file'))
    cmd_state_on()


def cmd_change_src_filename():
    """Sélecteur de fichiers pour spécifier le fichier source"""
    cmd_state_off()
    tix_Var_src_filename.set(tkinter.filedialog.askopenfilename(title='Source file'))
    cmd_state_on()


def cmd_load():
    """Charge et affiche le fichier source"""
    global content_file

    cmd_state_off()
    tix_Button_load.config(relief=tkinter.SUNKEN)
    content_file = load_file(tix_Var_src_filename.get())

    cmd_update()
    s = []
    if content_file[1] != None:
        for c in content_file[1]:
            if c == '\n':
                s.append('\\n')
            elif c == '\r':
                s.append('\\r')
            elif c == '\r\n':
                s.append('\\r\\n')
            else:
                s.append(chr(65533))
    tix_Label_newlines.config(text='file.newlines: ' + ', '.join(s))

    tix_Button_load.config(relief=tkinter.RAISED)
    cmd_state_on()


def cmd_quit():
    """Callback pour le Button 'Quit'"""
    global calc_state

    cmd_state_off()
    tix_Button_quit.config(relief=tkinter.SUNKEN)
    if tk.messagebox.askyesno('Quit?', 'Quit transfileTk?'):
        calc_state = False
        tix_root.quit()
    tix_Button_quit.config(relief=tkinter.RAISED)
    cmd_state_on()


def cmd_save():
    """Sauve dans le fichier destination
    (le fichier source est (re)chargé et (ré)affiché).
    Si le fichier existe déjà une confirmation est demandé"""
    global content_file

    cmd_state_off()
    tix_Button_save.config(relief=tkinter.SUNKEN)
    if tix_Var_dest_filename.get() == '':
        tk.messagebox.showerror(
            'Error',
            "The destination file '{0}' is wrong!".format(tix_Var_dest_filename.get()))
    elif ((not os.path.isfile(tix_Var_dest_filename.get()))
          or tk.messagebox.askyesno('Save?', """The file '{0}' already exist.

Save?""".format(tix_Var_dest_filename.get()))):
        content_file = load_file(tix_Var_src_filename.get())
        cmd_update()
        error = save_file(tix_Var_dest_filename.get(), content_file[0])
        if error:
            tk.messagebox.showerror('Error', error)
    tix_Button_save.config(relief=tkinter.RAISED)
    cmd_state_on()


def cmd_state_off():
    """Réactive les boutons"""
    for w in state_seq:
        w.config(state=tk.DISABLED)


def cmd_state_on():
    """Réactive les boutons"""
    for w in state_seq:
        w.config(state=tk.NORMAL)


def cmd_update():
    """Affiche le contenu de content_file"""
    tix_Text_file.config(state=tk.NORMAL)
    tix_Text_file.delete('1.0', tk.END)

    if content_file[2]:
        tix_Text_file.insert(tk.END, content_file[2], 'error')

    length = len(str(len(content_file[0])))  # nombre de chiffres du plus grand numéro de ligne

    for i in range(len(content_file[0])):
        if tix_Var_shownumline.get():
            s = str(i + 1)
            tix_Text_file.insert(tk.END, (' '*(length - len(s))) + s, 'numline')

        if not tix_Var_showendofline.get():
            tix_Text_file.insert(tk.END, content_file[0][i])
        else:
            for c in content_file[0][i]:
                tix_Text_file.insert(tk.END, c,
                                     None if c not in ('\n', '\r', '\r\n')
                                     else 'endofline')

    tix_Text_file.tag_config('numline', background='gray', foreground='white')
    tix_Text_file.tag_config('endofline', background='#eeeeee')
    tix_Text_file.tag_config('error', foreground='red')
    tix_Text_file.config(state=tk.DISABLED)



########
# Main #
########
# Interface graphique
tix_root.title('transfileTk')
tix_root.resizable(0,0)
tix_root.protocol('WM_DELETE_WINDOW', cmd_quit)


tix_t = tk.Frame(tix_root)
tix_t.pack()


# Panneau fichier source
tix_t2 = tk.Frame(tix_t)
tix_t2.pack(side=tk.LEFT)

tk.Label(tix_t2, text=chr(8211) + ' Source file ' + chr(8211)).pack()

tix_t3 = tk.Frame(tix_t2)
tix_t3.pack()

tix_t4 = tk.Entry(tix_t3, textvariable=tix_Var_src_filename)
tix_t4.pack(side=tk.LEFT)
state_seq.append(tix_t4)
tk.Frame(tix_t3, width=5).pack(side=tk.LEFT)
tix_t4 = tk.Button(tix_t3, text='F', command=cmd_change_src_filename)
tix_t4.pack()
state_seq.append(tix_t4)

tk.Label(tix_t2, text='Encoding:').pack()
tix_t3 = tk.tix.ComboBox(tix_t2, variable=tix_Var_src_encoding)
tix_t3.pack()
for s in ENCODING_STR:
    tix_t3.insert(tk.END, s)
state_seq.append(tix_t3)

tk.Label(tix_t2, text='Errors:').pack()
tix_t3 = tk.tix.ComboBox(tix_t2, variable=tix_Var_src_errors)
tix_t3.pack()
for s in ERRORS:
    tix_t3.insert(tk.END, s)
state_seq.append(tix_t3)

tk.Label(tix_t2, text='Newline:').pack()
tix_t3 = tk.tix.ComboBox(tix_t2, variable=tix_Var_src_newline)
tix_t3.pack()
for s in NEWLINE_SRC_STR:
    tix_t3.insert(tk.END, s)
state_seq.append(tix_t3)


# Panneau boutons et paramètres
tk.Frame(tix_t, width=10).pack(side=tk.LEFT)
tix_t2 = tk.Frame(tix_t)
tix_t2.pack(side=tk.LEFT)

tix_t3 = tk.Frame(tix_t2)
tix_t3.pack()

tix_Button_load = tk.Button(tix_t3, text='Load', command=cmd_load)
tix_Button_load.pack(side=tk.LEFT)
state_seq.append(tix_Button_load)

tk.Frame(tix_t3, width=5).pack(side=tk.LEFT)
tix_Button_save = tk.Button(tix_t3, text='Save', command=cmd_save)
tix_Button_save.pack(side=tk.LEFT)
state_seq.append(tix_Button_save)

tk.Frame(tix_t3, width=10).pack(side=tk.LEFT)
tix_Button_about = tk.Button(tix_t3, text='About', command=cmd_about)
tix_Button_about.pack(side=tk.LEFT)
state_seq.append(tix_Button_about)

tk.Frame(tix_t3, width=5).pack(side=tk.LEFT)
tix_Button_quit = tk.Button(tix_t3, text='Quit', command=cmd_quit)
tix_Button_quit.pack()
state_seq.append(tix_Button_quit)


tk.Frame(tix_t2, height=20).pack()
tix_t3 = tk.Checkbutton(tix_t2, text='Show number of line', var=tix_Var_shownumline,
                        command=cmd_update)
tix_t3.pack()
state_seq.append(tix_t3)

tix_t3 = tk.Checkbutton(tix_t2, text='Show end of line', var=tix_Var_showendofline,
                        command=cmd_update)
tix_t3.pack()
state_seq.append(tix_t3)


tk.Frame(tix_t2, height=20).pack()
if os.linesep == '\n':
    s = '\\n  (POSIX)'
elif os.linesep == '\r':
    s = '\\r  (Mac)'
elif os.linesep == '\r\n':
    s = '\\r\\n  (Windows)'
else:
    s = chr(65533)
tix_t3 = tk.Label(tix_t2, text='os.linesep: ' + s)
tix_t3.pack()
tk.tix.Balloon().bind_widget(tix_t3, msg='End of line used on this platform.')

tix_Label_newlines = tk.Label(tix_t2, text='file.newlines: ')
tix_Label_newlines.pack()
tk.tix.Balloon().bind_widget(tix_Label_newlines, msg='Ends of line encoutered in the source file.')

s = None


# Panneau fichier destination
tk.Frame(tix_t, width=10).pack(side=tk.LEFT)
tix_t2 = tk.Frame(tix_t)
tix_t2.pack(side=tk.LEFT)
tk.Label(tix_t2, text=chr(8211) + ' Destination file ' + chr(8211)).pack()

tix_t3 = tk.Frame(tix_t2)
tix_t3.pack()

tix_t4 = tk.Entry(tix_t3, textvariable=tix_Var_dest_filename)
tix_t4.pack(side=tk.LEFT)
state_seq.append(tix_t4)
tk.Frame(tix_t3, width=5).pack(side=tk.LEFT)
tix_t4 = tk.Button(tix_t3, text='F', command=cmd_change_dest_filename)
tix_t4.pack()
state_seq.append(tix_t4)

tk.Label(tix_t2, text='Encoding:').pack()
tix_t3 = tk.tix.ComboBox(tix_t2, variable=tix_Var_dest_encoding)
tix_t3.pack()
for s in ENCODING_STR:
    tix_t3.insert(tk.END, s)
state_seq.append(tix_t3)

tk.Label(tix_t2, text='Errors:').pack()
tix_t3 = tk.tix.ComboBox(tix_t2, variable=tix_Var_dest_errors)
tix_t3.pack()
for s in ERRORS:
    tix_t3.insert(tk.END, s)
state_seq.append(tix_t3)

tk.Label(tix_t2, text='Newline:').pack()
tix_t3 = tk.tix.ComboBox(tix_t2, variable=tix_Var_dest_newline)
tix_t3.pack()
for s in NEWLINE_DEST_STR:
    tix_t3.insert(tk.END, s)
state_seq.append(tix_t3)


# Zone d'affichage du fichier
tix_t = tk.Scrollbar(tix_root)
tix_t.pack(side=tk.RIGHT, fill=tk.Y)

tix_Text_file = tk.Text(tix_root, height=15, width=60, state=tk.DISABLED)
tix_Text_file.pack(side=tk.LEFT)

tix_t.config(command=tix_Text_file.yview)
tix_Text_file.config(yscrollcommand=tix_t.set)


#
tix_t = None
tix_t2 = None
tix_t3 = None

state_seq = tuple(state_seq)

tix_root.focus_force()
tix_root.mainloop()
