from __future__ import print_function
"""
For usage instructions, see below.
Based on labelpin, Version 1.0 of 2010/11/22
By Nathan Dunfield : http://dunfield.info
"""

import os, sys
try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk
from optparse import OptionParser
from subprocess import Popen, PIPE
from tempfile import NamedTemporaryFile

help_str = r"""Usage:  textack file
where file is an ".eps" file.

A window will appear with your image.  Simply click on it to generate
the label locations, which are output with the associated LaTeX code
in the terminal window.  When you're done, just close the window and
copy everything into your LaTeX file.

The resulting LaTeX code is intended for use with the "pinlabel" package:

http://www.ctan.org/tex-archive/help/Catalogue/entries/pinlabel.html

Requirements:

 * Python with Tkinter.  
 * GhostScript.

On OS X, the first is built in and the others are included with the
MacTeX-2010 distribution: http://www.tug.org/mactex/

On Linux, all are available as standard packages, with the first
called "python-tk" on Ubuntu/Debian and "tkinter" on
Fedora/Mandriva/PCLinuxOS.
"""

pinlabel_prefix_text = r"""\
% Created by TeXtack
\begin{figure}[htb]
\labellist
\small
\hair 3pt
"""

pinlabel_suffix_text = r"""\
\endlabellist
\centering
\includegraphics[scale=1.0]{%s}
\caption{  }
\label{fig:label}
\end{figure}"""

class PushPin(list):
    """
    A pushpin for pinning your label to the canvas.
    """
    deltas = {'Up': (0,-1), 'Down': (0,1), 'Left': (-1,0), 'Right': (1,0)}
    h_codes = ('r', '', 'l')
    v_codes = ('t', '', 'B', 'b')
    
    def __init__(self, coords, master):
        list.__init__(self, coords)
        self.master = master
        self.canvas = master.canvas
        self.content = tk.StringVar(master.window)
        self.content.set('$?$')
        self.entry = tk.Entry(master.window, textvariable=self.content)
        self.entry.bind('<FocusIn>', master.text_on)
        self.entry.bind('<FocusOut>', master.text_off)
        self.active=True
        self.dot = None
        self.box = None
        self.baseline = None
        self.r = master.hair*master.px_per_pt
        self.anchor=[1,1] 

    def __eq__(self, other):
        return sum([(x-y)*(x-y) for x, y in zip(self, other)]) < self.r**2

    def draw(self):
        self.erase()
        self.draw_dot()
        self.draw_box()

    def draw_dot(self):
        x, y = self
        r = self.r
        if self.active:
            self.dot = self.canvas.create_oval(
                x-r, y-r, x+r, y+r, outline='red', fill='')
        else:
            self.dot = self.canvas.create_oval(
                x-r, y-r, x+r, y+r, outline='black', fill='red')

    def draw_box(self):
        x, y = self
        r, pt = self.r, self.master.px_per_pt
        w, h = 5*pt, 11*pt
        L = x + (-r-w, -w/2, r)[self.anchor[0]]
        R = L + w
        B = y + (h+r, h/2, h/3, -r)[self.anchor[1]]
        T = B - h
        base = B-h/3
        self.box = self.canvas.create_rectangle(L,B,R,T)
        self.baseline = self.canvas.create_line(L,base,R,base)

    def erase(self):
        if self.dot:
            self.canvas.delete(self.dot)
            self.dot=None
        if self.box:
            self.canvas.delete(self.box)
            self.box=None
        if self.baseline:
            self.canvas.delete(self.baseline)
            self.baseline = None

    def activate(self):
        self.active=True
        self.entry.grid(row=1, column=0)
        self.draw()
            
    def deactivate(self):
        self.active=False
        self.entry.grid_forget()
        self.draw()

    def shift_box(self, sym):
        h, v = self.anchor
        if sym == 'Left': h = max(h-1, 0)
        elif sym == 'Right': h = min(h+1, 2)
        elif sym == 'Down': v = max(v-1, 0)
        elif sym == 'Up': v = min(v+1, 3)
        self.anchor = [h,v]
        self.draw()
        
    def shift_point(self, sym):
        x, y = self
        dx, dy = PushPin.deltas[sym]
        self.move(x+dx, y+dy)

    def move(self, x, y):
        if self.active:
            self[0], self[1] = x, y
            self.draw()

    def position_code(self):
        return PushPin.h_codes[self.anchor[0]] + PushPin.v_codes[self.anchor[1]]

    def label(self):
        return self.content.get()
    
class Pinner:
    """
    A canvas showing the figure, that you can pin labels onto.
    """
    def __init__(self, file_to_label, hair=3, px_per_pt=2):
        self.hair=hair
        self.pins = []
        self.active_pin = None
        self.dragging=True
        self.text_mode=False
        self.filename = file_to_label
        self.base_name, self.ext = os.path.splitext(file_to_label)
        self.px_per_pt = px_per_pt
        self.window = window = tk.Tk()
        window.protocol("WM_DELETE_WINDOW", self.done)            
        window.title(self.base_name)
        window.geometry('+%d+%d' % (100,100))
        self.tkpi = tkpi = self.tkphotoimage()
        self.W, self.H = W, H, = tkpi.width(), tkpi.height()
        self.pt_height = H/px_per_pt
        self.canvas = canvas = tk.Canvas(
            window, width=W, height=H, bd=0, highlightthickness=0)
        # should offset by BBLL
        canvas.create_image( (0,0), anchor="nw", image=tkpi)
        canvas.grid(row=0, column=0, columnspan=2,
                    padx=3*px_per_pt, pady=3*px_per_pt)
        canvas.bind("<ButtonPress-1>", self.click)
        canvas.bind("<ButtonRelease-1>", self.end_drag)
        canvas.bind("<Motion>", self.motion)
        strut = tk.Frame(height=30, width=0)
        strut.grid(row=1, column=1, sticky=tk.E)
        window.bind("<Shift-Key>", self.shift_key)
        window.bind("<Key>", self.key)
                    
    def set_active(self, pin):
        if pin is self.active_pin:
            return
        else:
            if self.active_pin:
                self.active_pin.deactivate()
            pin.activate()
            self.active_pin = pin

    def deactivate(self):
        self.text_off()
        if self.active_pin:
            self.active_pin.deactivate()
            self.active_pin = None

    def text_on(self, event=None):
        self.text_mode = True
        self.canvas.config(cursor='pencil')

    def text_off(self, event=None):
        self.text_mode = False
        self.canvas.config(cursor='')
        self.canvas.focus_set()
        
    def click(self, event):
        """
        Create a new pin, or activate an old one.
        """
        point = (event.x, event.y)
        if point in self.pins:
            Q = self.pins[self.pins.index(point)]
            self.set_active(Q)
        else:
            self.deactivate()
            P = PushPin(point, self)
            self.pins.append(P)
            self.set_active(P)
            P.draw()
        self.dragging = True
            
    def shift_key(self, event):
        if self.active_pin:
            if event.keysym in ('Up', 'Down', 'Left', 'Right'):
                self.active_pin.shift_point(event.keysym)
            return 'break'
    
    def key(self, event):
        if self.active_pin and not self.text_mode:
            if event.keysym in ('Up', 'Down', 'Left', 'Right'):
                self.active_pin.shift_box(event.keysym)
            elif event.keysym in ('BackSpace', 'Delete'):
                for n, pin in enumerate(self.pins):
                    if pin is self.active_pin:
                        self.pins.pop(n)
                        self.active_pin.erase()
                        self.active_pin.entry.grid_forget()
                        self.active_pin=None
                        break
            elif event.keysym == 'Escape':
                if self.active_pin:
                    self.deactivate()
            elif event.keysym == 'Tab':
                self.text_on()
                return
        if self.text_mode:
            if event.keysym == 'Return':
                self.text_off()
                
    def end_drag(self, event):
        self.dragging = False

    def motion(self, event):
        if self.dragging and self.active_pin:
            self.active_pin.move(event.x, event.y)
            
    def tkphotoimage(self):
        """
        Return a TkPhotoImage of our file.
        """
        ppm_file = NamedTemporaryFile(suffix='ppm')
        ppm_filename = ppm_file.name
        res = 72*self.px_per_pt
        if self.ext.lower() in ['.eps','.pdf']:
            # Use GhostScript to convert to a pixmap.
            process = Popen(['gs', '-sDEVICE=ppmraw', '-dEPSCrop', '-dNOPAUSE',
                             '-dBATCH' '-dSAFER' '-q', '-dAlignToPixels=0',
                             '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
                             '-r%d'%res, '-sOutputFile=%s'%ppm_filename,
                             self.filename], stdin=PIPE, stdout=PIPE)
            process.communicate()
        else: # Use ImageMagick and pray.
            os.system("convert %s %s" % (self.filename, ppm_filename))
        result = tk.PhotoImage(file=ppm_filename)
        ppm_file.close()
        return result
    
    def ps_coords(self, point):
        """
        Convert screen coordinates to PostScript coordinates.
        """
        x, y = point
        return float(x)/self.px_per_pt, self.pt_height - float(y)/self.px_per_pt
    
    def TeX(self):
        """
        Return TeX code that generates the labeled figure, as a string.
        """
        result = pinlabel_prefix_text
        for n, P in enumerate(self.pins):
            x, y = self.ps_coords(P)
            result += " \pinlabel {%s} [%s] at %g %g\n" % (
                P.label(), P.position_code(), x, y)
        result += pinlabel_suffix_text % self.base_name
        return result
    
    def done(self):
        """
        Close the window and print the TeX code to standard output.
        """
        self.window.destroy()
        print( self.TeX() )
