# Skencil - A Python-based interactive drawing program
# Copyright (C) 2003,2004,2005 by Bernhard Herzog
#
# Authors:
#       Markus Rechtien <markus@rechtien.de> 2003,2004
#       Bernhard Reiter <bernhard@intevation.de> 2005
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Skencil for details.

__version__ = "$Revision: $"

# $Source: $
# $Id: $

# TODO s
#  20051031 replace deprecated gtk.Combo with gtkComboBox or BH's 
#           Sketch.UI.gtkmisc.CommonComboBox or so.
#  20051031 make the print command adjustable, one idea is to
#           create an input line for it, showing the print command
#  20051031 locate the Menu entry within File better
#  20051031 Add check for poster binary on first import.
#  20051109 Make sure that raise SkencilError() actually pop up something.
#  20051109 The print result output should be shown in a windows

###Sketch Config
#type = PluginCompound
#class_name = 'PosterDialog'
#menu_text = 'Print Document as Poster'
#standard_messages = 1
#load_immediately = 1
###End

'''Skencil Tool dialog for printing large ps poster via the "poster" tool.

Taken from poster help:
----------------------------------------------------------------------

Usage: poster <options> infile

options are:
   -v:         be verbose
   -f:         ask manual feed on plotting/printing device
   -i<box>:    specify input image size
   -c<margin>: horizontal and vertical cutmargin
   -w<margin>: horizontal and vertical additional white margin
   -m<box>:    media paper size
   -p<box>:    output poster size
   -s<number>: linear scale factor for poster
   -P<pages>:  comma-separated list of tile pages to print
   -o<file>:   output redirection to named file

   At least one of -s -p -m is mandatory, and don't give both -s and -p
   <box> is like 'A4', '3x3letter', '10x25cm', '200x200+10,10p'
   <margin> is either a simple <box> or <number>%. Page lists are comma-
   separated, each element being a single page or a page range.
   (ex: 1,2-5,8,10-34)

   Defaults are: '-ma4', '-c5%', '-i<box>' read from input file.
                 and output written to stdout.

----------------------------------------------------------------------
'''

# We need os.path, os.popen and os.system
import os

# For building gui components
import gtk
# Some gui Constants
from gtk import FILL, EXPAND, SELECTION_MULTIPLE

# Translation and the PostScript rendering
from Sketch import _, PostScriptDevice, SketchError
# Determine the name of user
from Sketch.Lib import util
# Will be derived by the PosterDialog
from Sketch.UI.skpanel import SketchPanel


# Options for page sizes and examples for custom values
io_sizes = ['A5', 'A4', 'A3', 'A2', 'A1', 'A0', 
        'WIDTHxHEIGHTcm', 'WIDTHxHEIGHT+A,Bp', 'AxBletter']
# Help on input-output sizes - shown on the dialog
hints = "Size and Media is like 'A4',\n"  \
        "     '3x3letter', '10x25cm',\n"  \
        "     '200x200+10,10p'\n"         \
        "Scale is a number for linear\n " \
        "      scale factor.\n"


class BasicOptions(gtk.Frame):
    '''A frame containing the basic options for large format printouts.'''
    title = _("Print Document as Poster")

    def __init__(self, context, topdialog):
        '''Init the frame and setup some needed variables.'''
        gtk.Frame.__init__(self)
        self.save2file = 1
        self.rotate = 0
        self.context = context
        self.topdialog = topdialog
        self.create_frame()

    def create_frame(self):
        '''Set up the dialogs gui layout.'''
        topdlg = self.topdialog
        self.vbox = vbox = gtk.VBox()
        hbox = gtk.HBox(spacing = 5)
        hbox.set_border_width(5)
        hbox.set_spacing(5)

        # Table left side controls - input settings
        table = gtk.Table(4, 2)
        table.set_row_spacings(3)
        table.set_col_spacings(3)
        # Table content
        align = gtk.Alignment(xalign=0.0)
        label = gtk.Label(_('Input options:'))
        align.add(label)
        table.attach(align, 0, 2, 0, 1, EXPAND|FILL)
        label = gtk.Label(_('Size:'))
        table.attach(label, 0, 1, 1, 2, EXPAND|FILL)
        cbox = self.cb_isize = gtk.Combo()
        cbox.set_use_arrows(1)
        cbox.set_popdown_strings(io_sizes)
        cbox.entry.set_text(io_sizes[1])
        table.attach(cbox, 1, 2, 1, 2, EXPAND|FILL)
        # Add a helptext area
        tview = gtk.TextView()
        tbuf = tview.get_buffer()
        titer = tbuf.get_iter_at_line(0)
        tbuf.insert(titer, hints)
        tview.set_sensitive(0)
        frame = gtk.Frame(_("Help"))
        frame.add(tview)
        table.attach(frame, 0, 2, 2, 4, EXPAND|FILL, EXPAND|FILL, 10, 10)
        # Add the left table to the hbox
        hbox.pack_start(table, expand = 1, fill = 1)
        # Table right side controls - output settings
        table = gtk.Table(6, 3)
        table.set_row_spacings(2)
        # Table content
        align = gtk.Alignment(xalign=0.0)
        label = gtk.Label(_('Output options:'))
        align.add(label)
        table.attach(align, 0, 2, 0, 1, EXPAND|FILL)
        # Option scale output
        radio = self.rb_scale = gtk.RadioButton(None, _('Scale:'))
        radio.connect("toggled", self.OnRadioChoice, "scale")
        table.attach(radio, 0, 1, 1, 2)
        entry = self.e_scale = gtk.Entry()
        table.attach(entry, 1, 2, 1, 2, EXPAND|FILL)
        # Option output size
        radio = self.rb_size = gtk.RadioButton(radio, _('Size:'))
        radio.connect("toggled", self.OnRadioChoice, "size")
        table.attach(radio, 0, 1, 2, 3)
        cbox = self.cb_osize = gtk.Combo()
        cbox.set_use_arrows(1)
        cbox.set_popdown_strings(io_sizes)
        cbox.entry.set_text(io_sizes[4])
        table.attach(cbox, 1, 2, 2, 3, EXPAND|FILL)
        # option media size
        label = gtk.Label(_('Media:'))
        table.attach(label, 0, 1, 3, 4, EXPAND|FILL)
        cbox = self.cb_media = gtk.Combo()
        cbox.set_use_arrows(1)
        cbox.set_popdown_strings(io_sizes)
        cbox.entry.set_text(io_sizes[1])
        table.attach(cbox, 1, 2, 3, 4, EXPAND|FILL)
        # option save to file
        ckbut = self.ckb_save_to_file = gtk.CheckButton(_('Save to file'))
        ckbut.set_active(1)
        ckbut.connect("toggled", self.OnCkeckbox, None)
        table.attach(ckbut, 0, 2, 4, 5, EXPAND|FILL)
        ckbut = self.ckb_rotate = gtk.CheckButton(_('Rotate'))
        ckbut.connect("toggled", self.OnCkeckbox, None)
        table.attach(ckbut, 0, 2, 5, 6, EXPAND|FILL)

        # Add the right table to the hbox
        hbox.pack_start(table, expand = 1, fill = 1)
        # put the hbox at start of the top level vbox
        vbox.pack_start(hbox, expand = 1, fill = 1)

        # hbox with OK, CANCEL buttons
        table = gtk.Table(1, 2)
        table.set_row_spacings(3)
        button = gtk.Button(_("Close"))
        button.connect("clicked", topdlg.OnCancel)
        table.attach(button, 0, 1, 0, 1, EXPAND|FILL, 0, 40)
        button = gtk.Button(_("Print"))
        button.connect("clicked", topdlg.OnOK)
        table.attach(button, 1, 2, 0, 1, EXPAND|FILL, 0, 40)
        # put the table at the end off the top level vbox
        vbox.pack_start(table)
        # at last drop the top level vbox onto the frame
        self.add(vbox)
        # 
        self.rb_size.clicked()

    def OnRadioChoice(self, *args):
        '''Handles radio button events.

        Deletes the textfieldcontent of the unselected radiobutton.'''
        osize = self.cb_osize
        scale = self.e_scale
        if self.rb_scale.get_active():
            scale.set_sensitive(1)
            osize.set_sensitive(0)
        else:
            scale.set_text('')
            scale.set_sensitive(0)
            osize.set_sensitive(1)

    def OnCkeckbox(self, *args):
        self.save2file = self.ckb_save_to_file.get_active()
        self.rotate = self.ckb_rotate.get_active()

class ShellCmdAssemblyError(Exception):
    """Exception raised for errors assembling the shell command.

    Attributes:
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message


class PosterDialog(SketchPanel):
    '''Window for frames providing functions for printing in large formats.'''

    title = _("Print Document as Poster")

    def __init__(self, context):#, file):
        '''Init the Dialog - call superclass constructor.'''
        SketchPanel.__init__(self, context, self.title)
        self.context = context
        self.options = {}
        self.create_dialog()

    def create_dialog(self):
        '''Creates the dialog itself.'''
        self.set_default_size(225, 225)
        # For now we only have one page on the dialog.
        self.basic_page = BasicOptions(self.context, self)
        self.vbox.pack_start(self.basic_page, expand = 1, fill = 1)

    def save_epsdoc(self, document, file, rotate=0):
        '''Save the given document in EPS format to the given file.

        document    - the document to be written as PS
        file        - the file the document should be written to
        rotate      - wether to rotate the document 90 degree anticlockwise
        '''
        try:
            bbox = document.BoundingRect(visible = 0, printable = 1)
            title = os.path.basename(file.name)
            ps_dev = PostScriptDevice(file, as_eps = 1, 
                    bounding_box = tuple(bbox), rotate = rotate, 
                    For = util.get_real_username(),
                    CreationDate = util.current_date(), Title = title,
                    document = document)
            document.Draw(ps_dev)
        except:
            return 0
        return 1

    def _quote_shell_argument(self, arg):
        """Prepare arg to be used as shell argument.

        Will raise ShellCmdAssemblyError if arg contained a ' character.
        """
        if "'" in arg:
            raise ShellCmdAssemblyError( 
                _("Could not quote the following shell argument, " \
                    "because it already contains a ' char:") + str(arg))
        else:
            return "'"+arg+"'"


    def get_poster_cmd(self, fout=None, pipecmd=None):
        '''Build the poster execution command with arguments.

        Arguments:
            fout -- filename the output should go to
            pipecmd -- command the stdout should be piped to, pipecmd
                       is subject to shell substitutions

        Give only one of fout or pipecmd.
        Might raise ShellCmdAssemblyError.
        '''
        opt = self.options
        basics = self.basic_page
        if basics.rb_scale.get_active():
            if opt.has_key('p'):
                del(opt['p'])
            opt['s'] = basics.e_scale.get_text()
        else:
            if opt.has_key('s'):
                del(opt['s'])
            opt['p'] = basics.cb_osize.entry.get_text()
        opt['i'] = basics.cb_isize.entry.get_text()
        opt['m'] = basics.cb_media.entry.get_text()
        # put everything together
        cmd = 'poster '
        for i in opt.keys():
            cmd += "-%s %s " % (i, self._quote_shell_argument(opt[i]), )
        if fout and not pipecmd:
            cmd += '-o %s ' % (self._quote_shell_argument(fout), )
        if pipecmd and not fout:
            cmd += ' | ' + pipecmd
        if (pipecmd and fout) or (not pipecmd and not fout):
            raise ShellCmdAssemblyError(
                _("Conflicting output or piping options!"))
        return cmd

    def get_out_file(self):
        '''Ask the user for a file to save the document to.'''
        filename = ""
        dialog = gtk.FileSelection(_('Select a file:'))
        dialog.complete('*.ps')
        if dialog.run() == gtk.RESPONSE_OK:
            filename = dialog.get_filename()
        else: filename = 0
        dialog.destroy()
        return filename

    def RunDialog(self):
        '''Makes the dialog visible.'''
        self.visible = 1
        self.show_all()

    def OnOK(self, *args):
        basics = self.basic_page
        opt = self.options
        doc = self.context.document
        rotate = basics.rotate

        try:
            # assembling command line
            if basics.save2file:
                filename = self.get_out_file()
                if filename==0: 
                    # dialog has been cancelled
                    return
                if not (os.path.isfile(filename) and access(filename,os.W_OK)):
                    raise ShellCmdAssemblyError(_("No usable filename given!"))
                cmd = self.get_poster_cmd(fout=filename)
            else:
                cmd = self.get_poster_cmd(pipecmd="lpr")
        except ShellCmdAssemblyError, e:
            raise SketchError(e.message)
            print "UargS:"+e.message
            return

        outstr, instr = os.popen4(cmd)

        if not outstr or not instr:
            raise SketchError(_("Either 'poster' isn't installed or a " \
                    "temporary file cannot be written!"))
            return

        if not self.save_epsdoc(doc, outstr, rotate):
            raise SketchError(_("An error occurred during PS conversion!"))
            return
        try:
            outstr.close()

            # reading some of the possible command output (stdout and stderr) 
            r=instr.read(2000)
            if r:
                print " '%s' produced the following output:" % (cmd,)
                print r
            instr.close()
        except:
            raise SketchError(_("An error occurred while trying to print!"))

    def OnCancel(self, *args):
        '''Simply close this dialog.'''
        self.destroy()


def create_poster_dialog(context):
    '''Function called by the Skencil menu to open the Posterdialog.'''
    dialog = PosterDialog(context)
    dialog.RunDialog()


from Sketch.UI.skapp import main_menu as Menu
from Sketch.Editor import Add, Command
Add(Command("create_poster_dialog", "Poster Print", create_poster_dialog))
Menu.AddItem(("File",), "create_poster_dialog")
