#!/usr/bin/env python3
# Filename : rhinote.py

# Rhinote version 0.7.4  A simple "sticky notes" application; Linux version.

# Copyright 2006, 2010 by Marv Boyes - greyspace@tuxfamily.org
# http://rhinote.tuxfamily.org
# Please see the file COPYING for license details.

# 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 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., 51 Franklin St., Fifth Floor, Boston, MA  02110-1301 USA

import tkinter
import tkinter.filedialog as tkFileDialog
import tkinter.messagebox as tkMessageBox
import os
import subprocess


# the root window:
def Rhinote():
    r = tkinter.Tk(className='Rhinote')
    r.option_add('*font', '{Helvetica} 11')
    t = TextWidget(r, bg='#f9f3a9', wrap='word', undo=True)
    t.focus_set()
    t.pack(fill='both', expand=1)
    r.geometry('220x235')
    r.title('Rhinote')
    r.mainloop()


# the text widget, and all of its functions:
class TextWidget(tkinter.Text):

    def reset_status(self):
        self.edit_modified(0)
        self.unsaved_changes = False
        self.update_title()

    def update_title(self):
        title = ""
        if self.unsaved_changes:
            title += "* "
        if self.filename:
            title = title + os.path.basename(self.filename) + " - "
        title += "Rhinote"
        self.master.title(title)

    def modified(self, whatever=None):
        # This callback will be invoked even when *resetting* the
        # modified flag for the text widget, so it's critical to look
        # at the current value for the flag and act accordingly
        if self.edit_modified():
            self.unsaved_changes = True
        else:
            self.unsaved_changes = False
        self.update_title()

    def confirm_discard(self):
        if not self.unsaved_changes:
            return True
        return tkMessageBox.askokcancel(parent=self.master,
                                        title='Confirm',
                                        message='Discard unsaved changed?')

    def close(self):
        if not self.confirm_discard():
            return
        self.master.destroy()

    def select_all(self, whatever=None):
        self.tag_add(tkinter.SEL, '1.0', 'end')
        return "break"

    def save_file(self, whatever=None):
        if not self.filename:
            self.save_file_as()
        else:
            f = open(self.filename, 'w')
            f.write(self.get('1.0', 'end'))
            f.close()
            self.reset_status()
            # Uncomment the following lines if you want a
            # pop-up message every time you save a file:
            # tkMessageBox.showinfo(parent=self.master,
            #                       title='FYI',
            #                       message='File Saved.')
        return "break"

    def save_file_as(self, whatever=None):
        filename = tkFileDialog.asksaveasfilename(parent=self.master,
                                                  filetypes=self._filetypes)
        if not filename:
            return "break"
        self.filename = filename
        self.save_file()
        return "break"

    def open_file(self, whatever=None):
        if not self.confirm_discard():
            return "break"
        filename = tkFileDialog.askopenfilename(parent=self.master,
                                                filetypes=self._filetypes)
        if not filename:
            return "break"
        self.filename = filename
        f = open(self.filename, 'r')
        f2 = f.read()
        f.close()
        if f2.endswith('\n'):
            f2 = f2[:-1]
        self.delete('1.0', 'end')
        self.insert('1.0', f2)
        # Clear undo history. Loading a file means starting fresh
        self.edit_reset()
        self.reset_status()
        return "break"

    def new_window(self, event):
        Rhinote()

    def printfile(self, whatever=None):
        if not PRINTCOMMAND:
            tkMessageBox.showerror(parent=self.master,
                                   title='Print error',
                                   message='Print command (lp or lpr) not found')
            return "break"
        if not FORMATCOMMAND:
            tkMessageBox.showerror(parent=self.master,
                                   title='Print error',
                                   message='Format command (enscript) not found')
            return "break"
        # Prepare the format command
        formatargv = [FORMATCOMMAND]
        formatargv.extend(FORMATARGS)
        # Prepare the print command
        printargv = [PRINTCOMMAND]
        printargv.extend(PRINTARGS)
        # Spawn both commands, piping the output of the format command
        # to the input of the print command
        pp = subprocess.Popen(printargv, stdin=subprocess.PIPE)
        fp = subprocess.Popen(formatargv, stdin=subprocess.PIPE, stdout=pp.stdin)
        # Feed the contents of the text area to the format command and wait
        # for both commands to terminate
        fp.communicate(input=self.get('1.0', 'end').encode())
        pp.communicate()
        # Notify the user of the outcome of the printing operation
        if fp.returncode > 0 or pp.returncode > 0:
            tkMessageBox.showerror(parent=self.master,
                                   title='Print error',
                                   message='Printing failed')
        return "break"

    def help(self, whatever=None):
        tkMessageBox.showinfo(parent=self.master,
                              title='Rhinote Help',
                              message='''
Editing Commands
    Ctrl-x : Cut selected text
    Ctrl-c : Copy selected text
    Ctrl-v : Paste cut/copied text
    Ctrl-a : Select all text
    Ctrl-z : Undo
    Ctrl-Shift-z : Redo

File Commands
    Ctrl-o : Open file
    Ctrl-s : Save current note
    Ctrl-Shift-s : Save current note as <filename>
    Ctrl-p : Print current note
    Ctrl-n : Open new Rhinote

General
    Ctrl-h : Display this help window

Rhinote version 0.7.4
Free Software distributed under the GNU General Public License
http://rhinote.tuxfamily.org
''')
        return "break"

    def __init__(self, master, **kw):
        tkinter.Text.__init__(self, master, **kw)
        self.bind('<Control-n>', self.new_window)
        self.bind('<Control-o>', self.open_file)
        self.bind('<Control-s>', self.save_file)
        self.bind('<Control-S>', self.save_file_as)
        self.bind('<Control-p>', self.printfile)
        self.bind('<Control-h>', self.help)
        self.bind('<Control-a>', self.select_all)
        self.bind('<<Modified>>', self.modified)
        self.master = master
        self.master.protocol('WM_DELETE_WINDOW', self.close)
        self.filename = None
        self.unsaved_changes = False
        self._filetypes = [
            ('Text/ASCII', '*.txt'),
            ('Rhinote files', '*.rhi'),
            ('All files', '*'),
        ]


def which(cmd):
    # Abort immediately if PATH is not set
    path = os.getenv('PATH')
    if not path:
        return None
    # Look in all directories listed in PATH
    dirs = path.split(os.pathsep)
    path = None
    for d in dirs:
        f = os.path.join(d, cmd)
        # f must be an executable file
        if os.path.isfile(f) and os.access(f, os.X_OK):
            # Stop after the first success
            path = f
            break
    return path


# make it so:
if __name__ == '__main__':
    # Find print and format commands
    PRINTCOMMAND = which('lp')
    PRINTARGS = ['-t', 'Rhinote file']
    if not PRINTCOMMAND:
        PRINTCOMMAND = which('lpr')
        PRINTARGS = ['-T', 'Rhinote file']
    FORMATCOMMAND = which('enscript')
    FORMATARGS = ['-B', '--word-wrap', '-o', '-']

    Rhinote()
