#!/usr/bin/python3
# -*- coding: utf8 -*-

from __future__ import print_function
from __future__ import unicode_literals


# version 3.1.2 : bug fix : autoscale didn't work for a mix of portrait / landscape pages.
# version 3.1.1 : workaround for a bug appeared in Ubuntu 19 (see the OnDraw function)
# version 3.1
# fix a serious bug which prevented auto-scale to work.

# version 3.0.6, 09/05/2018
# Bug fixes
# Better Linux support
# No longer uses a temporary file, preview data now in memory

# version 3.0.5, 30 / 07 / 2017
# German Translation added

# version 3.0.4
# New feature : add page numbers (still experimental)
# Gui : dotted line in the middle of a booklet - Still to be improved
# No longer uses the tempfiles/preview.pdf temporary file.
# this is now handled in Memory. Bugs to fix on that feature.
# To understand the reason for which we have used new_from_bytes and not new_from_data, see here :
# https://stackoverflow.com/questions/45838863/gio-memoryinputstream-does-not-free-memory-when-closed
# Fix bug for display of red rectangles when the output page is rotated 90° or 270°

PB_version = "3.1.2"


"""

website : pdfbooklet.sourceforge.net

This software is a computer program whose purpose is to manipulate pdf files.

This software is governed by the CeCILL license under French law and
abiding by the rules of distribution of free software.  You can  use,
modify and/ or redistribute the software under the terms of the CeCILL
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".

As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited
liability.

In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and,  more generally, to use and operate it in the
same conditions as regards security.

The fact that you are presently reading this means that you have had
knowledge of the CeCILL license and that you accept its terms.

==========================================================================
"""


"""
TODO : enregistrer un projet  dans un répertoire avec caractères unicode
vérifier menuAdd

selection_s : L'usage de cette variable serait à vérifier. Est-ce que cela ne crée pas de la confusion ?
Pourquoi ne pas utiliser directement config["options"]["pageSelection"] qu'elle remplace ? Un peu plus long, mais plus facile à déboguer.
Problème surtout à mettre au point :
    Supposons une liste de fichiers ouverts dans lesquels on a fait une sélection.
    Si on ouvre le gestionnaire de fichier a ajoute un fichier à la liste, la sélection est remise à zéro.
    Ce n'est pas bon, il faudrait seulement ajouter les pages du nouveau fichier.
    C'est assez compliqué à gérer si des fichiers ont été supprimés. Une routine de comparaison
    avant et après avoir ouvert le gesionnaire devrait faire le travail.

TODO :

Autoscale : Distinguer les options : pour les pages et global ? Pas sûr que ce soit utile.
Quand un répertoire est sélectionné, avertir
quand on ouvre un fichier, et puis ensuite un projet qui a plusieurs fichiers, pdfshuffler n'est pas bien mis à jour
popumenu rotate : les valeurs de la fenêtre transformations ne sont pas mises à jour.

bugs
fichier ini : ouvrir un fichier, ouvrir le fichier ini correspondant. Ne rien changer, fermer,
le fichier ini est mis à jour à un moment quelconque et souvent toutes les transformations sont remises à zéro.
Dans la même manipulation , quand on ouvre, il arrive que les modifications d'une page soient conservées
et pas celle de l'autre page (en cahier)

slow mode : si la première feuille est faite entièrement de pages blanches générées (par exemple 2 pour
        pages blanches au début dans une configuraiton à deux pages),
        line 4285, in createNewPdf
        pages[i - 1].append(dataz)
        IndexError: list index out of range

petits défauts

quand on clique sur les boutons de global rotation, double update du preview (problème des boutons radio)

améliorations
Le tooltip pour le nom de fichier pourrait afficher les valeurs réelles que donneront les différents paramètres

"""



"""
    EXPLANATIONS OF THE WORKFLOW

    The structure of the program is easy to understand.
    Everything runs around the "config" dictionary which defines how the source pdf files
    must be placed in the output file.
    The content of this dictionary may be viewd at any time by the command "Save project"
    which builds an ini file from this dictionary.

    The program has two parts :
            1) The PdfRenderer class : It receives the config dictionary,
                and from its content builds the output file, applying the necessary
                transormations to the source pages. These transformations, in Pdf,
                are always handled byt transformation matrices. See Pdf specifications
                for details.
            2) The gui, whose only purpose is to build the config dictionary in an easy way.

    Workflow :
        1) Normal process is : - the gui creates and updates the config dict.
                               - When the Go button is pressed, the config dictionary
                                 is sent to PdfRenderer which builds the output file
        2) Preview process : To create the preview, a similar process is used.
                               - the config dictionary is sent to PdfRenderer, with an
                                 additional parameter which indicates a page number
                               - PdfRenderer creates a pdf file in memory which contains a single page.
                               - This page is displayed in the gui by Poppler.

    Inside config, the pages are named in two different ways :
        - Absolute : 2:25 designates a single page, page 25 of the second file.
        - Positional : 2,1 (line, column) designates any page which is placed
          on line 2, column 1

    What renders things complicated is that pdf counts pages from the bottom
    left, starting by 0, which is not user friendly. So the program has to convert
    data in a readable format.

    Another complication is that Pdf defines the center of rotation at the lower left corner,
    which is not user friendly. The rotate function handles this and shifts the image to create
    a centered rotation.

    Transformations

    1) When the user clicks on the preview, the selectPage function is launched.
       a) From the mouse coordinates, it determines the page clicked, and builds a page identifier
       which is a list of six values :
           - row and column (Pdf format)
           - file number and page number
           - row and column if the output page is rotated
       then it updates the selected pages list.
       b) it launchs area_expose to update the display
       c) it extracts from config the transformations already defined for this page
          and fills in the gtkEntry widgets which contains the transformations

    2) When the user changes a value in these widgets, the transformationApply function is launched.
       It reads the values in the gui and updates the config dictionary
       Then it launchs the preview function which will update the preview



    HOWTO

    to add a parameter, three steps are necessary :
        1) add the code which will use the parameter
        2) add a control in Glade
        3) add a line in makeinifile to write the parameter in the project file
        4) add a line in setupGui to setup the gui from the ini file.


"""

"""
Ubuntu 2016 - dépendences

python3-gi
python3-gi-cairo
gir1.2-gtk-3.0
gir1.2-poppler-0.18

Installation de pyinstaller

sudo pip3 install pyinstaller
le paquet python-dev est aussi nécessaire (mais pip le trouve)


"""

import time, math, string, os, sys, re, shutil, site
#print(sys.version)
sys.path.append(os.path.abspath(os.path.dirname(__file__)))

try :
    import configparser     # Python 3
    from configparser import ConfigParser, RawConfigParser
except :
    from ConfigParser import ConfigParser, RawConfigParser
import io
from collections import defaultdict, OrderedDict
import subprocess
from subprocess import Popen, PIPE
from ctypes import *
import threading
import tempfile, io
import copy
##import urllib
##from urllib.parse import urlparse
##from urllib.request import urljoin


from optparse import OptionParser
import traceback


import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Poppler', '0.18')

from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Poppler
from gi.repository import Pango
from gi.repository import Gio, GLib
from gi.repository import cairo


Gtk.rc_parse("./gtkrc")

from pdfbooklet.PyPDF2_G import PdfFileReader, PdfFileWriter
import pdfbooklet.PyPDF2_G.generic as generic

# from pdfbooklet import *
from pdfbooklet.files_chooser import Chooser

import locale       #for multilanguage support
import gettext
import pdfbooklet.elib_intl3 as elib_intl3
elib_intl3.install("pdfbooklet", "share/locale")

debug_b = 0



def join_list(my_list, separator) :
    mydata = ""
    if isinstance(my_list, list) :
        for s in my_list :
            mydata += s + separator
    elif isinstance(my_list, dict) :
        for s in my_list :
            try :
                item1 = unicode(my_list[s], "utf-8")
            except :
                item1 = my_list[s]
            mydata += item1 + separator
    crop = len(separator) * -1
    mydata = mydata[0:crop]
    return mydata

def get_value(dictionary, key, default = 0) :

    if not key in dictionary :
        dictionary[key] = default
        return default
    else :
        return dictionary[key]


def unicode2(string, dummy = "") :

    if sys.version_info[0] == 2 :
        if isinstance(string,unicode) :
            return string

    try :
        return unicode(string,"utf_8")
    except :
        try :
#               print string, " est ecrit en cp1252"
            return unicode(string,"cp1252")
        except :
            return string       # Is this the good option ? Return False or an empty string ?
            #return "inconnu"

def printExcept() :
    a,b,c = sys.exc_info()
    for d in traceback.format_exception(a,b,c) :
        print(d, end=' ')

def bool_test(value) :
    if isinstance(value, str) :
        try :
            value = int(value)
        except :
            if value.strip().lower() == "true" :
                return True
            else :
                return False

    return bool(value)

def alert(message, type = 0) :

        dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING,
                                     Gtk.ButtonsType.CLOSE , message)
        dialog.run()
        dialog.destroy()

def showwarning(title, message) :
    """
      GTK_MESSAGE_INFO,
      GTK_MESSAGE_WARNING,
      GTK_MESSAGE_QUESTION,
      GTK_MESSAGE_ERROR,
      GTK_MESSAGE_OTHER
    """

    resetTransform_b = False

    dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL , Gtk.MessageType.WARNING,
                                 Gtk.ButtonsType.CLOSE , title)

    dialog.format_secondary_text(message)
    if "transformWindow" in app.arw :
        app.arw["transformWindow"].set_keep_above(False)
        resetTransform_b = True
    dialog.set_keep_above(True)
    dialog.run()
    dialog.destroy()
    if resetTransform_b == True :
        app.arw["transformWindow"].set_keep_above(True)


def askyesno(title, string) :


    dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL , Gtk.MessageType.QUESTION,
                               Gtk.ButtonsType.NONE, title)
    dialog.add_button(Gtk.STOCK_YES, True)
    dialog.add_button(Gtk.STOCK_NO, False)
    dialog.format_secondary_text(string)
    dialog.set_keep_above(True)
    rep = dialog.run()
    dialog.destroy()
    return rep

def ask_text(parent, message, default=''):
    """
    Display a dialog with a text entry.
    Returns the text, or None if canceled.
    """
    d = Gtk.MessageDialog(parent,
                          Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
                          Gtk.MessageType.QUESTION,
                          Gtk.ButtonsType.OK_CANCEL,
                          message)
    entry = Gtk.Entry()
    entry.set_text(default)
    entry.show()
    d.vbox.pack_end(entry, True, True, 0)
    entry.connect('activate', lambda _: d.response(Gtk.ResponseType.OK))
    d.set_default_response(Gtk.ResponseType.OK)

    r = d.run()
    text = entry.get_text()
    if sys.version_info[0] == 2 :
        text = text.decode('utf8')
    d.destroy()
    if r == Gtk.ResponseType.OK:
        return text
    else:
        return None




class myConfigParser() :
    def __init__(self) :
        pass

    def read(self,
            iniFile_s,
             encoding = "utf8",
             comments = False) :
        # ----
        # read ini file and creates a dictionary
        # @param iniFile_s : ini file path
        # @param comments : if True, comments are included in the config. Otherwise they are skipped
        # @return : True if successful, False otherwise


        myconfig = OrderedDict()
        if not os.path.isfile(iniFile_s) :
            return False

        try :                       # if pdfbooklet.cfg is invalid, don't block the program
            if sys.version_info[0] == 3 :
                fileIni = open(iniFile_s, "r", encoding = "utf8")
            else :
                fileIni = open(iniFile_s, "r")
            # If BOM present, skip the first three bytes
            isBOM_s = fileIni.read(3)
            if isBOM_s == chr(239) + chr(187) + chr(191) :  # There is a BOM, skips it
                pass
            else :
                fileIni.seek(0)                          # No BOM, come back to beginning of file

        except :
            myconfig = OrderedDict()
            myconfig["mru"] = OrderedDict()
            myconfig["mru2"] = OrderedDict()
            myconfig["options"] = OrderedDict()
            return myconfig

        section_s = ""
        while True :
            record_s = fileIni.readline()
            if record_s == "" :                     # end of file
                break
            # format line : strip and replace possible \ by /
            record_s = record_s.strip()
            if sys.version_info[0] == 2 :
                record_s = record_s.decode("utf8")
            record_s = record_s.replace("\\", "/")      # TODO : or better : formatPath()
            # If the  line is a section
            if record_s[0:1] == "[" and record_s[-1:] == "]" :      # section
                section_s = record_s[1:-1]
                myconfig[section_s] = OrderedDict()
            else :
                # Skip useless lines
                if section_s == "" :            # comment in the beginning of the file
                    continue
                if len(record_s) == 0 :         # empty line
                    continue
                if record_s[0:1] == "#" :       # comment
                    comment_b = True
                else :
                    comment_b = False
                if comments == False :          # Skip comments
                    if comment_b == True :
                        continue


                # otherwise, store data in section
                # TODO : comments
                record_data = record_s.split("=")
                if len(record_data) > 1 :
                    key = record_data[0].strip()
                    linedata = record_data[1].strip()
                    if linedata == "False" :
                        linedata = False
                    if linedata == "True" :
                        linedata = True
                    myconfig[section_s][key] = linedata
        return myconfig

    def write(self, myconfig, filename) :
        if sys.version_info[0] == 3 :
            iniFile = open(filename, "w", encoding = "utf8")
        else :
            iniFile = open(filename, "w")
        for a in myconfig :
            iniFile.write("[" + a + "]\n")
            for b in myconfig[a] :
                value = myconfig[a][b]
                if value == True :
                    value = '1'
                elif value == False :
                    value = '0'
                data1 = (b + " = " + value + "\n").encode("utf8")  # En python 3 cette ligne convertit en bytes !!!
                data1 = (b + " = " + value + "\n")
                iniFile.write(data1)
            iniFile.write("\n")
        iniFile.close()
        return True

class TxtOnly :
    def __init__(self,
                render,
                pdfList = None,
                pageSelection = None):

        global config, rows_i, columns_i, step_i, sections, output, input1, adobe_l, inputFiles_a, inputFile_a
        global numfolio, prependPages, appendPages, ref_page, selection, PSSelection
        global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l
        global ouputFile, optionsDict, selectedIndex_a, selected_page, deletedIndex_a, app
        global arw
##        elib_intl.install("pdfbooklet", "share/locale")

        if None != pdfList :
            inputFiles_a = pdfList
            self.loadPdfFiles()
        else :
            inputFiles_a = {}
            inputFile_a = {}
        self.permissions_i = -1     # all permissions
        self.password_s = ""
        rows_i = 1
        columns_i = 2
        urx_i = 200
        ury_i = 200
        optionsDict = {}
        adobe_l = 0.3527

        self.radioSize = 1
        self.radioDisp = 1
        self.repeat = 0
        self.booklet = 1
        self.righttoleft = 0
        self.delete_rectangle = []

    def openProject2(self, filename_u) :
        # Called by OpenProject and OpenMru (in case the selected item was a project)
        global config, openedProject_u, preview_b, project_b

        if os.path.isfile(filename_u):
            openedProject_u = filename_u
##            self.arw["window1"].set_title(u"Pdf-Booklet  [ " + PB_version + " ] - " + filename_u)
            preview_b = False
            project_b = True
            self.parseIniFile(filename_u)
            preview_b = True
            project_b = False
            return True


    def readNumEntry(self, entry, widget_s = "") :

        if isinstance(entry, int) :
            return float(entry)
        elif isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")
        if value == "" : value = 0
        try :
            value = float(value)
        except :
            showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
            return None

        return value


    def readmmEntry(self, entry, widget_s = "", default = 0) :
        global adobe_l

        value = ""
        if isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")
        if (value == "") :
            value = default
        else :
            try :
                value = float(value) / adobe_l
            except :
                showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                return None
        return value

    def readPercentEntry(self, entry, widget_s = "") :

        value = ""
        if sys.version_info[0] == 2 and isinstance(entry, unicode) :
                value = entry
        elif isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")
        if (value == "") :
            value = 100
        else :
            value.replace("%", "")
            try :
                value = float(value) / 100
                if value < 0 :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s)
                    return None
            except :
                showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                return None
        return value

    def readIntEntry(self, entry, widget_s = "", type_i = 0, default = 0) :
        # type = 0 : accepts all values >= 0
        # type = 1 : accepts all values > 0
        # type = -1 : accepts any integer, positive or negative
        # type = 2 : optional. Don't warn if missing, but warn if invalid (not integer)

        value = ""
        if isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")


        try :
            value = int(value)
            if type_i == 0 :
                if value < 0 :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be >= 0. Aborting \n") % widget_s)
                    return None
            elif type_i == 1 :
                if value < 1 :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s)
                    return None
        except :
            if value == "" :
                if type_i == 2 :
                    pass
                else :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                    return None
            else :
                showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                return None
        if value == "" :
            return default
        elif value == 0 and default > 0 :
            return default
        return value

    def readBoolean(self, entry) :

        value = ""

        if isinstance(entry, str) :
            if entry.strip().lower() == "true" :
                value = True
            else :
                value = False
        elif isinstance(entry, bool) :
            return entry
        else :
            value = entry.get_text()
        try :
            if int(value) < 1 :
                return False
            else :
                return True
        except :
            showwarning(_("Invalid data"), _("Invalid data for %s - must be 0 or 1. Aborting \n") % widget_s)


    def parseIniFile(self, inifile = "") :

        global config, rows_i, columns_i, step_i, cells_i, input1, adobe_l
        global numfolio, prependPages, appendPages, ref_page, selection
        global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, inputFile, inputFiles_a
        global startup_b


        config = parser.read(inifile)


        # migration to 2.4 format : copy xxxx1 values to xxxx and delete xxxx1
        for section in config :
            for option in config[section] :
                if option in["htranslate1", "vtranslate1", "scale1", "rotate1", "xscale1", "yscale1"] :  # Clean no longer used data
                    if option[:-1] in config[section] :
                        config[section][option[:-1]] = config[section][option]
                        del config[section][option]



        # store in dictionary
        self.pagesTr = config
        if not "options" in config :
            config["options"] = OrderedDict()

        for section in config :
            if not section in self.pagesTr :
                self.pagesTr[section] = OrderedDict()

        # inputs

        if startup_b == 0 :
            if "options" in config:
                if "inputs" in config["options"] :


                    temp1 = config["options"]["inputs"]
                    inputFiles_a = {}
                    for filename in temp1.split("|") :
                        if os.path.isfile(filename) :
                            pdfFile = PdfFileReader(open(filename, "rb"))
                            numpages = pdfFile.getNumPages()
                            path, shortFileName = os.path.split(filename)
                            i = len(inputFiles_a)
                            inputFiles_a[i + 1] = filename

                    self.loadPdfFiles()
                    if "pageselection" in config["options"] :
                            self.selection_s = config["options"]["pageselection"]



        # variables
        if "options" in config:
            if "booklet" in config["options"] :
                self.booklet = int(config["options"]["booklet"])


        # multi-line entries

        if "options" in config:
            if "userLayout" in config["options"] :
                layout_s = config["options"]["userLayout"]
                config["options"]["userLayout"] = layout_s.replace("/", "\n")
                (a,b,c,d) = self.parse_user_layout(layout_s)
                self.imposition = a



    def setOption(self, option, default = "") :
        if option in config["options"] :
            result = config["options"][option]
        else :
            result = default
        if isinstance(default, int) :
            try :
                return int(result)
            except :
                return default
    def parse_user_layout(self, layout_s) :

            if layout_s.strip() == "" :
                return ([], 0 , 0 , [])

            layout_s = layout_s.replace("/", "\n")
            lines = layout_s.split("\n")
            imposition = []
            lines2 = []
            for line in lines :
                if line.strip() == "" :     # correct errors : ignore blank lines
                    continue
                if line[0:1] == "#" :       # ignore comments
                    continue
                if line[0:4] == "====" :    # New sheet
                    imposition.append(lines2)
                    lines2 = []
                else :
                    lines2.append(line)
            if len(lines2) > 0 :
                imposition.append(lines2)

            numrows = len(lines2)
            cols = lines2[0].split(",")
            numcols = 0
            for a in cols :
                if a.strip() != "" :
                    numcols += 1

            imposition2 = []
            for lines2 in imposition :
                pages = []
                for line in lines2 :
                    line = line.split(",")
                    for a in line :
                        if a.strip() != "" :         # correct errors : ignore trailing comma
                            pages.append(a.strip())
                imposition2.append(pages)
            self.imposition = imposition2

            return (imposition2, numrows, numcols, pages)

    def loadPdfFiles(self) :
        global inputFile_a, inputFiles_a, pagesIndex_a, refPageSize_a

        i = 1
        inputFile_a = {}
        inputFile_details = {}
        for key in inputFiles_a :
            val = inputFiles_a[key]
            if os.path.isfile(val) :
                inputFile_a[val] = PdfFileReader(open(val, "rb"))
                inputFile_details[val] = {}
                if inputFile_a[val].getIsEncrypted() :
                    inputFile_details[val]["encrypt"] = True
                    if not hasattr(inputFile_a[val], "_decryption_key") :   # if not already decrypted
                        password = get_text(None, _("Please, enter the password for this file"))
                        if password != None :
                            password = password.encode("utf8")
                            inputFile_a[val].decrypt(password)     # Encrypted file
                            if key == 1 :           # we get permissions and password from the first file
                                (a,b,self.permissions_i) = inputFile_a[val].getPermissions()
                                self.password_s = password
                        inputFile_details[val]["password"] = password
                selectedIndex_a = {}
                deletedIndex_a = {}

                i += 1



    def output_page_size(self, radiosize, ref_file = 1, ref_page = 0, logdata = 1) :


        global config, rows_i, columns_i, sections, output, adobe_l, inputFiles_a, inputFile_a
        global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l, outputScale, refPageSize_a

        # Ouput page size
        if ref_file in inputFiles_a :
            fileName = inputFiles_a[ref_file]
            fileName = unicode2(fileName)
            try :
                page0 = inputFile_a[fileName].getPage(ref_page)
            except :
                print(_("The reference page is invalid. We use the first page"))
                page0 = inputFile_a[fileName].getPage(0)

            llx_i=page0.mediaBox.getLowerLeft_x()
            lly_i=page0.mediaBox.getLowerLeft_y()
            urx_i=page0.mediaBox.getUpperRight_x()
            ury_i=page0.mediaBox.getUpperRight_y()

            urx_i=float(urx_i) - float(llx_i)
            ury_i=float(ury_i) - float(lly_i)

            refPageSize_a = [urx_i, ury_i]
        else :
            alert(_("Reference page invalid, there is no file n°" + str(ref_file)))
            return False


        #££self.print2 (_("Size of source file =    %s mm x %s mm ") % (int(urx_i * adobe_l), int(ury_i * adobe_l)), 1)

        oWidth_i = urx_i * columns_i
        oHeight_i = ury_i * rows_i

        if radiosize == 1 :
            mediabox_l = [oWidth_i, oHeight_i]
        elif radiosize == 2 :                         # size = no change
            if oWidth_i < oHeight_i :                           # set orientation
                mediabox_l = [urx_i, ury_i]
            else :
                mediabox_l = [ury_i, urx_i]

            # calculate  the scale factor
            deltaW = mediabox_l[0] / oWidth_i
            deltaH = mediabox_l[1] / oHeight_i
            if deltaW < deltaH :
                outputScale = deltaW
            else :
                outputScale = deltaH


        elif radiosize == 3 :         # user defined

                customX = self.readNumEntry(app.arw["outputWidth"], _("Width"))
                if customX == None : return False
                customY = self.readNumEntry(app.arw["outputHeight"], _("Height"))
                if customY == None : return False


                mediabox_l = [ customX * (1 / adobe_l), customY * (1 / adobe_l)]


                # calculate  the scale factor
                deltaW = mediabox_l[0] / oWidth_i
                deltaH = mediabox_l[1] / oHeight_i
                if deltaW < deltaH :
                    outputScale = deltaW
                else :
                    outputScale = deltaH


        outputUrx_i = mediabox_l[0]
        outputUry_i = mediabox_l[1]

        app.arw["info_fichier_sortie"].set_text(_("%s mm x %s mm ") % (int(outputUrx_i * adobe_l), int(outputUry_i * adobe_l)))



class dummy:
    def __init__(self) :

        self.pagesTr = {}
        self.arw = {}


class gtkGui:
    # parameters :
    # render is an instance of pdfRenderer
    # pdfList is a dictionary of path of pdf files : { 1:"...", 2:"...", ... }
    # pageSelection is a list of pages in the form : ["w:x", ... , "y:z"]
    def __init__(self,
                    render,
                    pdfList = None,
                    pageSelection = None):

        global config, rows_i, columns_i, step_i, sections, output, input1, adobe_l, inputFiles_a, inputFile_a
        global numfolio, prependPages, appendPages, ref_page, selection, PSSelection
        global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l
        global ouputFile, optionsDict, selectedIndex_a, selected_page, selected_pages_a, selectedeletedIndex_a, app

        elib_intl3.install("pdfbooklet", "share/locale")

        if None != pdfList :
            inputFiles_a = pdfList
            ini.loadPdfFiles()
        else :
            inputFiles_a = {}
            inputFile_a = {}
        self.permissions_i = -1     # all permissions
        self.password_s = ""
        rows_i = 1
        columns_i = 2
        step_i = 1
        urx_i = 200
        ury_i = 200
        optionsDict = {}
        adobe_l = 0.3527


        areaAllocationH_i = 400
        areaAllocationW_i = 400
        self.freeze_b = False
        self.preview_scale = 1
        self.dev1 = ""  # for development needs

        selectedIndex_a = {}
        selected_page = None
        selected_pages_a = []
        deletedIndex_a = {}


        app = self
        self.render = render
        self.ar_pages = []
        self.ar_layout = []
        self.previewPage = 0
        self.clipboard= {}
        self.shuffler = None
        self.imposition = []

        self.initdrag = []
        self.enddrag = []

        self.backup = []
        self.backup_index = 0
        self.backup_command = True


        self.widgets = Gtk.Builder()
        #self.widgets.set_translation_domain('pdfbooklet')
        self.widgets.add_from_file(sfp2('data/pdfbooklet3.glade'))
        arWidgets = self.widgets.get_objects()
        self.arw = {}
        for z in arWidgets :
            try :
                name = Gtk.Buildable.get_name(z)
                self.arw[name]= z
                z.set_name(name)
            except :
                pass


        #autoconnect signals for self functions
        self.widgets.connect_signals(self)
        self.arw["drawingarea1"].connect('draw', self.OnDraw)

        self.autoscale = self.arw["autoscale"]
        self.area = self.arw["drawingarea1"]
        self.settings = self.arw["settings"]
        self.overwrite = self.arw["overwrite"]
        self.noCompress = self.arw["noCompress"]
        self.slowmode = self.arw["slowMode"]
        self.righttoleft = self.arw["righttoleft"]
        self.status = self.arw["status"]


        self.window1 = self.arw["window1"]
        self.window1.show_all()
        self.window1.set_title("Pdf-Booklet  [ " + PB_version + " ]")
##        self.window1.connect("destroy", lambda w: Gtk.main_quit())
        self.window1.connect("destroy", self.close_application)

        """
        To change the cursor :
        watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
        self.window1.get_window().set_cursor(watch_cursor)

##        display =  self.window1.get_display()
##        watch_cursor = Gdk.Cursor.new_from_name(display, "default")

        watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
        watch_cursor = Gdk.Cursor(Gdk.CursorType.CROSS)
        self.window1.get_window().set_cursor(watch_cursor)
        """

        self.mru_items = {}
        self.menuAdd()

        self.selection_s = ""

        # Global transformations
        self.Vtranslate1 = self.arw["vtranslate1"]
        self.scale1 = self.arw["scale1"]
        self.rotation1 = self.arw["rotation1"]
        self.thispage = self.arw["thispage"]
        self.evenpages = self.arw["evenpages"]
        self.oddpages = self.arw["oddpages"]



        self.area.show()
        self.pagesTr = {}

# ############ Setup drag motion for drawingarea1 ##############


        # setup drag
##        targets = Gtk.TargetList.new([])
##        targets.add_text_targets(0)
##
##        self.area.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [],
##            Gdk.DragAction.COPY)
##        self.area.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
##        self.area.drag_source_set_target_list(targets)
##        self.area.drag_dest_set_target_list(targets)

        # self.area is connected to drag_motion in glade




# ##################### Themes #################################


        menu_themes = Gtk.Menu()
        mem_menu_name = ""
        # Get the list of gtkrc-xxx files in "data", extract the name, and add items to menu
        themes_dict = {}

        themes_dir = os.path.join(prog_path_u, "share/themes")
        if os.path.isdir(themes_dir) :


            for a in os.listdir(themes_dir) :   # TODO ££
                        rcpath = os.path.join(prog_path_u,a)
                        themes_dict[a] = rcpath
            themes_list = themes_dict.keys()
    ##        themes_list.sort()

            # Extract similar short names to build submenus
            mem_short_name = ""
            submenus = []
            for menu_name in themes_list :
                short_name = menu_name.split("-")[0]
                if short_name == mem_short_name :
                    if short_name not in submenus :
                        submenus.append(short_name)
                mem_short_name = short_name

            # Build first level menu
            sub_dict = {}
            to_del = []
            keys = themes_dict.keys()
    ##        keys.sort()
            for menu_name in submenus :
                if len(menu_name.strip()) == 0 :
                    continue
                sub_dict[menu_name] = Gtk.MenuItem(menu_name)
                menu_themes.append(sub_dict[menu_name])
                sub_dict[menu_name].show()


                submenu = Gtk.Menu()
                submenu.show()
                for key in keys :
                    rcpath = themes_dict[key]
                    short_name = key.split("-")[0]
                    if short_name == menu_name :
                        commandes = Gtk.MenuItem(key)
                        submenu.append(commandes)
                        commandes.connect("activate", self.change_theme, rcpath, key)
                        commandes.show()
                        to_del.append(key)

                sub_dict[menu_name].set_submenu(submenu)

            # delete used keys and add the remaining to main menu

            for key in to_del :
                del themes_dict[key]
            keys = themes_dict.keys()
    ##        keys.sort()

            for menu_name in keys :
                if len(menu_name.strip()) == 0 :
                    continue
                rcpath = themes_dict[menu_name]
                commandes = Gtk.MenuItem(menu_name)
                menu_themes.append(commandes)
                commandes.connect("activate", self.change_theme, rcpath, menu_name)
                commandes.show()

            self.arw["themes"].set_submenu(menu_themes)
            aaa = 1


    def change_theme(self, widget, path, theme) :

        try:
            settings_location = os.path.join(site.getsitepackages()[1], "gnome/etc/gtk-3.0/settings.ini")
        except :
            settings_location = os.path.join(prog_path_u, "etc/gtk-3.0/settings.ini")
        f1 = open(settings_location, "w")
        f1.write("[Settings]\n")
        f1.write("gtk-theme-name = " + theme)
        f1.close()
        alert(_("You must restart the program to apply the new theme."))






    # this small function returns the type of a widget
    def widget_type(self, widget) :
        try :
            z = widget.class_path()
            z2 = z.split(".")[-1]
            return z2
        except:
            return False


    def gtk_delete(self, source=None, event=None):
        Gtk.main_quit()

    def close_application(self, widget, event=None, mydata=None):
        """Termination"""
        if self.shuffler != None :
            self.shuffler.close_application("")
            self.shuffler = None

        if Gtk.main_level():
            self.arw["window1"].destroy()
            Gtk.main_quit()
            Gdk.threads_leave()

        #os._exit(0)
        return False

    def file_manager(self,widget):
        global inputFiles_a
        mrudir = self.read_mru2()
        if mrudir == "" :
            mrudir = prog_path_u
        self.chooser = Chooser(inputFiles_a, share_path_u, mrudir)
        inputFiles_a = self.chooser.inputFiles_a
        if len(inputFiles_a) == 0 :
            return
        # add file(s) to most recently used
        self.mru(inputFiles_a)
        self.chooser.chooser.destroy()
        self.chooser = None
        if self.shuffler:
            self.shuffler.model.clear()
            self.shuffler.pdfqueue = []
            self.shuffler.nfile = 0
            for key in inputFiles_a :
                self.shuffler.add_pdf_pages(inputFiles_a[key])
            # TODO : N'est à faire que si la liste des fichiers a changé
            self.shuffler.rendering_thread.pdfqueue = self.shuffler.pdfqueue

        ini.loadPdfFiles()
        app.selection_s = ""

        self.previewUpdate()


    def FormatPath (
            self,
            path,
            typePath = 0) :
        # Replaces // and \\ by /, but preserves the initial // necessary in urls on a network

        if path[0:2] == "//" or path[0:2] == ["\\"] :
            prefix_s = "//"
            path = path[2:]
        else :
            prefix_s = ""

        if typePath == 1 :
            path = path.replace(":", "")
            prefix_s = ""
        path = path.replace("\\", "/")
        path = path.replace("//", "/")
        return(prefix_s + path)




    def openProject(self, widget, name = "") :
        global config, openedProject_u, preview_b, project_b



        old_dir = self.read_mru2()

        gtk_chooser = Gtk.FileChooserDialog(title=_('Import...'),
                                        action=Gtk.FileChooserAction.OPEN,
                                        buttons=(Gtk.STOCK_CANCEL,
                                                  Gtk.ResponseType.CANCEL,
                                                  Gtk.STOCK_OPEN,
                                                  Gtk.ResponseType.OK))
        gtk_chooser.set_current_folder(old_dir)
        gtk_chooser.set_select_multiple(False)

        filter_all = Gtk.FileFilter()
        filter_all.set_name(_('All files'))
        filter_all.add_pattern('*')
        gtk_chooser.add_filter(filter_all)

        filter_ini = Gtk.FileFilter()
        filter_ini.set_name(_('INI files'))
        filter_ini.add_pattern('*.ini')
        gtk_chooser.add_filter(filter_ini)
        gtk_chooser.set_filter(filter_ini)

        response = gtk_chooser.run()
        if response == Gtk.ResponseType.OK:
            filename = gtk_chooser.get_filename()
            filename_u = unicode2(filename, "utf-8")
            self.mru(filename)
            ini.openProject2(filename_u)
            ini.loadPdfFiles()
            self.setupGui()
            self.arw["previewEntry"].set_text("1")
            self.previewUpdate()
            self.write_mru2(filename_u)       # write the location of the opened directory in the cfg file



##        elif response == Gtk.RESPONSE_CANCEL:
##            print(_('Closed, no files selected'))
        gtk_chooser.destroy()

    def openMru(self, widget) :
        global config, openedProject_u, preview_b, project_b
        global inputFiles_a

        widget_name = widget.get_name()
        filenames_list_s = self.mru_items[widget_name][1]

        # are we opening a project file ?
        filename_u = unicode2(filenames_list_s[0], "utf-8")
        extension_s = os.path.splitext(filename_u)[1]
        if extension_s == ".ini" :
            ini.openProject2(filename_u)
            self.selection_s = config["options"]["pageSelection"]
            ini.loadPdfFiles()
            self.setupGui()
            self.arw["previewEntry"].set_text("1")
            self.previewUpdate()
            return
        else :
            ini.parseIniFile(sfp3("pdfbooklet.cfg"))     # reset transformations
            self.setupGui(sfp3("pdfbooklet.cfg"))

        inputFiles_a = {}

        for filename_s in filenames_list_s :
            filename_u = unicode2(filename_s, "utf-8")
            extension_s = os.path.splitext(filename_u)[1]
            i = len(inputFiles_a)
            inputFiles_a[i + 1] = filename_u


        ini.loadPdfFiles()
        app.selection_s = ""
        self.previewUpdate()


        if self.shuffler:
            self.shuffler.model.clear()
            self.shuffler.pdfqueue = []
            self.shuffler.nfile = 0
            self.shuffler.npage = 0
            for key in inputFiles_a :
                self.shuffler.add_pdf_pages(inputFiles_a[key])
            # TODO : N'est à faire que si la liste des fichiers a changé
            self.shuffler.rendering_thread.pdfqueue = self.shuffler.pdfqueue
            #for row in self.shuffler.model:
            #        row[6] = False






    def saveProject(self, widget) :
        global openedProject_u

        if openedProject_u :
            self.saveProjectAs("", openedProject_u)
        else :
            self.saveProjectAs("")


    def saveProjectAs(self, widget, filename_u = "") :
        global config, openedProject_u

        if filename_u == "" :

            old_dir = self.read_mru2()

            gtk_chooser = Gtk.FileChooserDialog(title=_('Save project...'),
                                            action=Gtk.FileChooserAction.SAVE,
                                            buttons=(Gtk.STOCK_CANCEL,
                                                      Gtk.ResponseType.CANCEL,
                                                      Gtk.STOCK_SAVE,
                                                      Gtk.ResponseType.ACCEPT))
            gtk_chooser.set_do_overwrite_confirmation(True)
            gtk_chooser.set_current_folder(old_dir)
            gtk_chooser.set_current_name("untitled document")
            # or chooser.set_filename("untitled document")

            response = gtk_chooser.run()

            if response == Gtk.ResponseType.CANCEL:
##                print(_('Closed, no files selected'))
                gtk_chooser.destroy()
                return

            elif response == Gtk.ResponseType.ACCEPT:
                filename = gtk_chooser.get_filename()
                filename_u = unicode2(filename, "utf-8")
                if filename_u[-4:] != ".ini" :
                    filename_u += ".ini"
                gtk_chooser.destroy()
                self.mru(filename_u)



        openedProject_u = filename_u

        for section in self.pagesTr :
            if not section in config :
                config[section] = self.pagesTr[section]



        # update with last selections in the gui
        # perhaps we could also update first pagesTr
        self.makeIniFile()
##        for section in out_a :
##            config[section] = out_a[section]


        #config.write(iniFile)
        self.write_ordered_config(filename_u)
        self.write_mru2(filename_u)        # write the location of the opened directory in the cfg file

    def write_ordered_config(self, filename_u) :
        global config

        if sys.version_info[0] == 3 :
            iniFile = open(filename_u, "w", encoding = "utf8")
        else :
            iniFile = open(filename_u, "w")

        # store data in an ordered dictionary
        out_a = OrderedDict()
        sections_list = list(config.keys())
        for section1 in["options", "mru", "mru2", "output"] :
            if section1 in config :
                out_a[section1] = OrderedDict()
                for option in config[section1] :
                    value = config[section1][option]
                    if value == 'False' :
                        value = False
                    elif value == 'True' :
                        value = True

                    out_a[section1][option] = value
                sections_list.remove(section1)

        sections_list.sort()
        for section1 in sections_list :
            out_a[section1] = OrderedDict()
            for option in config[section1] :
                    value = config[section1][option]
                    if value == 'False' :
                        value = False
                    elif value == 'True' :
                        value = True
                    out_a[section1][option] = value

        # write data
        for section2 in out_a :
            iniFile.write("[" + section2 + "]\n")
            for option2 in out_a[section2] :
                iniFile.write(option2 + " = " + str(out_a[section2][option2]) + "\n")

        iniFile.close()




    def mru(self, filenames_a) :

        mrudir = ""
        if isinstance(filenames_a, dict) :
            filenames = join_list(filenames_a, "|")
            if 1 in filenames_a :
                mrudir = os.path.split(filenames_a[1])[0]
        else :
            filenames = filenames_a
            mrudir = os.path.split(filenames_a)[0]


        configtemp = parser.read(sfp3("pdfbooklet.cfg"))

####    if configtemp.has_section(section) == False :
####        configtemp.add_section(section)
####    configtemp.set(section,option,value)
##
##
        if not "mru" in configtemp :
            configtemp["mru"] = {}
        if not "mru2" in configtemp :
            configtemp["mru2"] = {}

        # cancel if already present
        temp_a = []
        for index in ["mru1", "mru2", "mru3", "mru4"] :
            if index in configtemp["mru"] :
                if filenames == configtemp["mru"][index] :
                    return


        # shift mru
        if "mru3" in configtemp["mru"] :
            configtemp["mru"]["mru4"] = configtemp["mru"]["mru3"]
        if "mru2" in configtemp["mru"] :
            configtemp["mru"]["mru3"] = configtemp["mru"]["mru2"]
        if "mru1" in configtemp["mru"] :
            configtemp["mru"]["mru2"] = configtemp["mru"]["mru1"]
        # set the new value
        configtemp["mru"]["mru1"] = filenames
        configtemp["mru2"]["mru1"] = mrudir
##        f = open(sfp3("pdfbooklet.cfg"),"w")
##        configtemp.write(f)
##        f.close()
        parser.write(configtemp, sfp3("pdfbooklet.cfg"))
        configtemp = None
        self.menuAdd()


    def mru_python2(self, filenames_a) :

        mrudir = ""
        if isinstance(filenames_a, dict) :              # several files selected
            filenames = join_list(filenames_a, "|")
            if 1 in filenames_a :                       # At least one file
                mrudir = os.path.split(filenames_a[1])[0]
        else :
            filenames = filenames_a
            mrudir = os.path.split(filenames_a)[0]

        #filenames = filenames.encode('utf-8')

        configtemp = parser.read(sfp3("pdfbooklet.cfg"))

        if configtemp.has_section("mru") == False :
            configtemp.add_section("mru")
        if configtemp.has_section("mru2") == False :
            configtemp.add_section("mru2")

        # cancel if already present
        temp_a = []
        for index in ["mru1", "mru2", "mru3", "mru4"] :
            if configtemp.has_option("mru",index) :
                if filenames == configtemp.get("mru",index) :
                    return

        try :
            # shift mru
            if configtemp.has_option("mru","mru3") :
                configtemp.set("mru","mru4",configtemp.get("mru","mru3"))
            if configtemp.has_option("mru","mru2") :
                configtemp.set("mru","mru3",configtemp.get("mru","mru2"))
            if configtemp.has_option("mru","mru1") :
                configtemp.set("mru","mru2",configtemp.get("mru","mru1"))
            # set the new value
##            filenames_s = filenames.encode("utf-8")
##            mrudir_s = mrudir.encode("utf-8")

            configtemp.set("mru","mru1",filenames)
            configtemp.set("mru2","mru2",mrudir)
            f = open(sfp3("pdfbooklet.cfg"),"w", encoding = "utf8")
            configtemp.write(f)
            f.close()
            configtemp = None
            self.menuAdd()
        except :
            printExcept()
            alert("problem in mru (line 700)")



    def read_mru2(self) :

        if os.path.isfile(sfp3("pdfbooklet.cfg")) :
            configtemp = parser.read(sfp3("pdfbooklet.cfg"))

            mru_dir = ""
            if "mru2" in configtemp :
                if "mru2" in configtemp["mru2"] :
                    mru_dir = configtemp["mru2"]["mru2"]

            configtemp = None
            return mru_dir

    def read_mru2_python2(self) :

        if os.path.isfile(sfp3("pdfbooklet.cfg")) :
            configtemp = parser.read(sfp3("pdfbooklet.cfg"))

            try :
                mru_dir = configtemp.get("mru2","mru2")
            except :
                mru_dir = ""

            configtemp = None
            return mru_dir

    def write_mru2(self, filename_u) :

        if os.path.isfile(sfp3("pdfbooklet.cfg")) :
            configtemp = parser.read(sfp3("pdfbooklet.cfg"))

        if not "mru2" in configtemp :
            configtemp["mru2"] = OrderedDict()

        (path_u, file_u) = os.path.split(filename_u)
        configtemp["mru2"]["mru2"] = path_u
        parser.write(configtemp, sfp3("pdfbooklet.cfg"))


    def menuAdd(self) :
        # Called by function mru, adds an entry to the menu


        configtemp = parser.read(sfp3("pdfbooklet.cfg"))
        if configtemp == False :
            return

        if "mru" in configtemp :
            for item in ["mru1", "mru2", "mru3", "mru4"] :
                if item in configtemp["mru"] :
                    filepath_list_s = configtemp["mru"][item]
                    filepath_list = filepath_list_s.split("|")
                    temp1 = []
                    for filepath_s in filepath_list :
                        filepath_s = self.FormatPath(filepath_s)
                        path_s,filename_s = os.path.split(filepath_s)
                        temp1 += [filename_s]
                    menu_entry_s = join_list(temp1, ", ")
                    if len(menu_entry_s) > 40 :
                        menu_entry_s = menu_entry_s[0:40] + "..."

                    self.mru_items[item] = [menu_entry_s, filepath_list]        # contains real path, used to open files
                    self.arw[item].set_label(menu_entry_s)                      # displayed menu text





    def pdfBooklet_doc(self, widget) :

        userGuide_s = "documentation/" + _("Pdf-Booklet_User's_Guide.pdf")
        if 'linux' in sys.platform :
            subprocess.call(["xdg-open", userGuide_s])
        else:
            os.startfile(sfp(userGuide_s))


    def popup_rotate(self, widget):
        # Called by popup menu (right clic on preview)
        global selected_page, rows_i


        # We get the value from the widget name (the 3 menu options use the same function)
        wname = widget.get_name()
        match = re.match("rotate(\d*)", wname)
        value = match.group(1)

        # Code below is just an adaptation of function "transormationsApply"
        if selected_page == None :
            showwarning(_("No selection"), _("There is no selected page. \nPlease select a page first. "))
            return
        # selected_page 4 and 5 contain the correct page reference, including the global rotation.
        humanReadableRow_i = rows_i - selected_page[4]
        Id = str(str(humanReadableRow_i) + "," + str(selected_page[5] + 1))

        pageId = str(selected_page[2]) + ":" + str(selected_page[3])

##        # if transformation is for this page only, use page ref instead of position ref
##        if self.thispage.get_active() == 1 :
##            Id = pageId

        if (Id in config) == False :
            config[Id] = {}
            config[Id]["htranslate"] = '0'
            config[Id]["vtranslate"] = '0'
            config[Id]["scale"] = '100'
            config[Id]["xscale"] = '100'
            config[Id]["yscale"] = '100'
            config[Id]["vflip"] = False
            config[Id]["hflip"] = False





        config[Id]["rotate"] = str(value)

        self.preview(self.previewPage, 0)



    def __________________INI_FILE() :
        pass


    def saveDefaults(self, dummy) :

        out_a = self.makeIniFile()
        iniFile = open(sfp3("pdfbooklet.cfg"), "w")
        for a in out_a :
            if not a in ["mru", "mru2", "options"] :
                continue
            iniFile.write("[" + a + "]\n")
            for b in out_a[a] :
                value = out_a[a][b]
                if value == True :
                    value = '1'
                elif value == False :
                    value = '0'
                iniFile.write(b + " = " + value + "\n")
            iniFile.write("\n")
        iniFile.close()

    def readNumEntry(self, entry, widget_s = "") :

        if sys.version_info[0] == 2 and isinstance(entry, unicode) :
                value = entry
        elif isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")
        if value == "" : value = 0
        try :
            value = float(value)
        except :
            showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
            return None

        return value


    def readmmEntry(self, entry, widget_s = "", default = 0) :
        global adobe_l

        value = ""
        if sys.version_info[0] == 2 and isinstance(entry, unicode) :
                value = entry
        elif isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")
        if (value == "") :
            value = default
        else :
            try :
                value = float(value) / adobe_l
            except :
                showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                return None
        return value

    def readPercentEntry(self, entry, widget_s = "") :

        value = ""
        if sys.version_info[0] == 2 and isinstance(entry, unicode) :
                value = entry
        elif isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")
        if (value == "") :
            value = 100
        else :
            value.replace("%", "")
            try :
                value = float(value) / 100
                if value < 0 :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s)
                    return None
            except :
                showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                return None
        return value

    def readIntEntry(self, entry, widget_s = "", type_i = 0, default = "") :
        # type = 0 : accepts all values >= 0
        # type = 1 : accepts all values > 0
        # type = -1 : accepts any integer, positive or negative
        # type = 2 : optional. Don't warn if missing, but warn if invalid (not integer)

        value = ""
        if isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        value = value.replace(",", ".")


        try :
            value = int(value)
            if type_i == 0 :
                if value < 0 :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be >= 0. Aborting \n") % widget_s)
                    return None
            elif type_i == 1 :
                if value < 1 :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s)
                    return None
        except :
            if value == "" :
                if type_i == 2 :
                    pass
                else :
                    showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                    return None
            else :
                showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s)
                return None
        if value == "" :
            return default
        return value

    def readBoolean(self, entry) :

        value = ""
        if sys.version_info[0] == 2 :
            if isinstance(entry, unicode) :
                value = entry
        elif isinstance(entry, str) :
            value = entry
        else :
            value = entry.get_text()
        try :
            if int(value) < 1 :
                return False
            else :
                return True
        except :
            showwarning(_("Invalid data"), _("Invalid data for %s - must be 0 or 1. Aborting \n") % widget_s)






    def readGui(self, logdata = 1) :

        global config, rows_i, columns_i, step_i, sections, output, input1, input2, adobe_l, inputFiles_a, inputFile_a
        global numfolio, prependPages, appendPages, ref_page, selection
        global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l, outputScale, refPageSize_a

        if self.freeze_b == True :
            return

        # Read the gui and update the config dictionary
        self.makeIniFile()

        outputFile = config["options"]["output"]
        outputScale = 1

        rows_i = self.readIntEntry(self.arw["entry11"], _("rows"), 1)
        #if rows_i == None : return False
        columns_i = self.readIntEntry(self.arw["entry12"], _("columns"), 1)
        #if columns_i == None : return False
        if (rows_i < 1) :
                rows_i = 1
        if (columns_i < 1) :
                columns_i = 1


        if ini.repeat == 1 :
            step_i = self.readIntEntry(self.arw["entry15"], _("step"), 1)
            if step_i == None : return False
            if (step_i < 1) :
                step_i = 1
        else :
            step_i = rows_i * columns_i



        numfolio = self.readIntEntry(self.arw["entry13"], _("folios"))
        prependPages = self.readIntEntry(self.arw["entry32"], _("Leading blank pages"))
        if prependPages == None : return False
        appendPages = self.readIntEntry(self.arw["entry33"], _("Trailing blank pages"))
        if appendPages == None : return False
        selection = self.selection_s


        if self.arw["radiosize1"].get_active() == 1 :
            radiosize = 1
        elif self.arw["radiosize2"].get_active() == 1 :
            radiosize = 2
        if self.arw["radiosize3"].get_active() == 1 :
            radiosize = 3

        referencePage = config["options"]["referencePage"]
        if referencePage.strip() == "" or referencePage == "0" :
            referencePage = "1"
        temp1 = referencePage.split(":")
        try :
            if len(temp1) == 2 :
                ref_file = int(temp1[0])
                ref_page = int(temp1[1])
            else :
                ref_file = 1
                ref_page = int(temp1[0])
        except :
            alert(_("Invalid value for reference page, please correct and try again"))
            return False
        if ref_page > 0 :
            system_ref_page = ref_page - 1          # system numbering start from 0 and not 1
        ini.output_page_size(radiosize, ref_file, system_ref_page, logdata)

        return True



    def setOption(self, option, widget, section = "options") :
        global config

        if section in config :
            if option in config[section] :
                value = config[section][option]
                z = widget.class_path()[1]
                z2 = z.split(".")
                z3 = z2[-1]
                if z3 == "GtkSpinButton" :
                    mydata = value
                    mydata = mydata.replace(",",".")
                    if mydata.strip() != "" :
                        widget.set_value(float(mydata))
                elif z3 == "GtkTextView" :
                    widget.get_buffer().set_text(value)
                elif z3 == "GtkCheckButton" :
                    try :
                        value = int(value)
                    except :
                        pass
                    if bool_test(value) == True :
                        widget.set_active(True)

                else :
                    widget.set_text(value)




    def setupGui(self, inifile = "") :

        global config, rows_i, columns_i, step_i, cells_i, input1, adobe_l
        global numfolio, prependPages, appendPages, ref_page, selection
        global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, inputFile, inputFiles_a
        global startup_b


        self.freeze_b = True            # prevent update of the display, which would trigger readGui and corrupt the data

        # set radio buttons

        if "presets" in config["options"] :
            temp1 = config["options"]["presets"]
            self.arw[temp1].set_active(True)

        if "size" in config["options"] :
            temp1 = config["options"]["size"]
            self.arw[temp1].set_active(True)

        if "presetOrientation" in config["options"] :
            temp1 = config["options"]["presetOrientation"]
            self.arw[temp1].set_active(True)

        if "globalRotation"  in config["options"] :
            temp1 = config["options"]["globalRotation"]
            self.arw[temp1].set_active(True)


        self.setOption("rows", self.arw["entry11"])
        self.setOption("columns", self.arw["entry12"])
        self.setOption("step", self.arw["entry15"])
        self.setOption("numfolio", self.arw["entry13"])
        self.setOption("prependPages", self.arw["entry32"])
        self.setOption("appendPages", self.arw["entry33"])
        self.setOption("referencePage", self.arw["entry31"])
        self.setOption("creep", self.arw["creep"])
        self.setOption("output", self.arw["entry2"])
        self.setOption("width", self.arw["outputWidth"])
        self.setOption("height", self.arw["outputHeight"])
        self.setOption("userLayout", self.arw["user_layout"])

        self.setOption("htranslate", self.arw["htranslate2"], "output")
        self.setOption("vtranslate", self.arw["vtranslate2"], "output")
        self.setOption("scale", self.arw["scale2"], "output")
        self.setOption("rotate", self.arw["rotation2"], "output")
        self.setOption("xscale", self.arw["xscale2"], "output")
        self.setOption("yscale", self.arw["yscale2"], "output")
        self.setOption("vflip", self.arw["vflip2"], "output")
        self.setOption("hflip", self.arw["hflip2"], "output")

        self.setOption("font_size", self.arw["numbers_font_size"], "page_numbers")
        self.setOption("start_from", self.arw["numbers_start_from"], "page_numbers")
        self.setOption("bottom_margin", self.arw["numbers_bottom_margin"], "page_numbers")


        # set check boxes

        if "advanced"  in config["options"] :
            if config.getint("options", "advanced") == 1 : self.advanced.set_active(1)
            else : self.advanced.set_active(0)
            self.guiAdvanced()
        if "autoscale"  in config["options"] :
            if bool_test(config["options"]["autoscale"]) == True :
                self.autoscale.set_active(1)
            else :
                self.autoscale.set_active(0)
        if "autoRotate"  in config["options"] :
            if int(config["options"]["autoRotate"]) == 1 : self.autorotate.set_active(1)
            else : self.autorotate.set_active(0)

        if "showPdf"  in config["options"] :
            if bool_test(config["options"]["showPdf"]) == True : self.arw["show"].set_active(1)
            else : self.arw["show"].set_active(0)
        if "saveSettings"  in config["options"] :
            if bool_test(config["options"]["saveSettings"]) == True : self.settings.set_active(1)
            else : self.settings.set_active(0)

        if "noCompress"  in config["options"] :
            if bool_test(config["options"]["noCompress"]) == True : self.noCompress.set_active(1)
            else : self.noCompress.set_active(0)
        if "overwrite"  in config["options"] :
            if bool_test(config["options"]["overwrite"]) == True : self.overwrite.set_active(1)
            else : self.overwrite.set_active(0)

        if "righttoleft"  in config["options"] :
            if bool_test(config["options"]["righttoleft"]) == True : self.righttoleft.set_active(1)
            else : self.righttoleft.set_active(0)
        if "slowmode"  in config["options"] :
            if bool_test(config["options"]["slowmode"]) == True : self.slowmode.set_active(1)
            else : self.slowmode.set_active(0)

        if "page_numbers" in config :
            if "page_numbers"  in config["page_numbers"] :
                if bool_test(config["page_numbers"]["page_numbers"]) == True :
                    self.arw["page_numbers"].set_active(1)
                else :
                    self.arw["page_numbers"].set_active(0)

        # set TextView

        buf = self.arw["textview1"].get_buffer()
        if "userLayout" in config["options"] :
            buf.set_text(config["options"]["userLayout"])


        self.freeze_b = False


    def makeIniFile(self, inifile = "") :
        # Reads the gui and updates config dictionary

        global config, rows_i, columns_i, step_i, cells_i, input1, adobe_l
        global numfolio, prependPages, appendPages, ref_page, selection
        global numPages, pagesSel, inputFile
        global out_a

        out_a = config

        config["options"]["rows"] = self.arw["entry11"].get_text()
        config["options"]["columns"] = self.arw["entry12"].get_text()
        config["options"]["booklet"] = str(ini.booklet)
        config["options"]["step"] = self.arw["entry15"].get_text()
        config["options"]["numfolio"] = self.arw["entry13"].get_text()
        config["options"]["prependPages"] = self.arw["entry32"].get_text()
        config["options"]["appendPages"] = self.arw["entry33"].get_text()
        config["options"]["referencePage"] = self.arw["entry31"].get_text()
        config["options"]["creep"] = self.arw["creep"].get_text()
        config["options"]["pageSelection"] = self.selection_s    # TODO : is this a good idea ?
        buf = self.arw["user_layout"].get_buffer()
        start, end  = buf.get_bounds()
        layout_s = buf.get_text(start, end, True)
        config["options"]["userLayout"] = layout_s.replace("\n", "/")

        temp1 = ""
        for key in inputFiles_a :
                val = inputFiles_a[key]
                temp1 += val + '|'

        config["options"]["inputs"] = temp1
        config["options"]["output"] = self.arw["entry2"].get_text()
        config["options"]["repeat"] = str(self.arw["entry15"].get_text())
        config["options"]["showPdf"] = str(self.arw["show"].get_active())
        config["options"]["saveSettings"] = str(self.settings.get_active())
        config["options"]["autoscale"] = str(self.autoscale.get_active())
        config["options"]["width"] = str(self.arw["outputWidth"].get_text())
        config["options"]["height"] = str(self.arw["outputHeight"].get_text())

        config["options"]["noCompress"] = str(self.noCompress.get_active())
        config["options"]["righttoleft"] = str(self.righttoleft.get_active())
        config["options"]["overwrite"] = str(self.overwrite.get_active())
        config["options"]["slowmode"] = str(self.slowmode.get_active())

        if not "output" in config :
            config["output"] = OrderedDict()
        config["output"]["htranslate"] = self.arw["htranslate2"].get_text()
        config["output"]["vtranslate"] = self.arw["vtranslate2"].get_text()
        config["output"]["scale"] = self.arw["scale2"].get_text()
        config["output"]["rotate"] = self.arw["rotation2"].get_text()
        config["output"]["xscale"] = self.arw["xscale2"].get_text()
        config["output"]["yscale"] = self.arw["yscale2"].get_text()
        config["output"]["vflip"] = self.arw["vflip2"].get_active()
        config["output"]["hflip"] = self.arw["hflip2"].get_active()

        if not "page_numbers" in config :
            config["page_numbers"] = OrderedDict()
        config["page_numbers"]["page_numbers"] = self.arw["page_numbers"].get_active()
        config["page_numbers"]["font_size"] = self.arw["numbers_font_size"].get_text()
        config["page_numbers"]["start_from"] = self.arw["numbers_start_from"].get_text()
        config["page_numbers"]["bottom_margin"] = self.arw["numbers_bottom_margin"].get_text()
##        config["output"]["yscale"] = self.arw["yscale2"].get_text()

        # radio buttons

        group = self.arw["radiopreset1"].get_group()
        for a in group :
            if a.get_active() == True :
                config["options"]["presets"] = a.get_name()

        group = self.arw["radiosize1"].get_group()
        for a in group :
            if a.get_active() == True :
                config["options"]["size"] = a.get_name()

        group = self.arw["presetOrientation1"].get_group()
        for a in group :
            if a.get_active() == True :
                config["options"]["presetOrientation"] = a.get_name()

        group = self.arw["globalRotation0"].get_group()
        for a in group :
            if a.get_active() == True :
                config["options"]["globalRotation"] = a.get_name()

        # most recently used

        # TODO : if file exists (ici et ailleurs)
        configtemp = parser.read(sfp3("pdfbooklet.cfg"))

        if "mru" in configtemp :
            for option in configtemp["mru"] :
                if "mru" in config :
                    config["mru"][option] = configtemp["mru"][option]





        return config


    def _______________________PRESETS() :
        pass


    def guiPresets(self, radiobutton = 0, event = None) :

        global startup_b, project_b, preview_b

        if radiobutton != 0 :
            if radiobutton.get_active() == 0 :  # signal is sent twice, ignore one of them
                return
        if project_b == True :  # Don't change values if we are loading a project
            return

        preview_b = False   # prevent multiple preview commands due to signals emitted by controls
        presetOrientation_i = self.arw["presetOrientation1"].get_active()

        if self.arw["radiopreset1"].get_active() == 1 : # single booklet
            if presetOrientation_i == 1 :
                self.presetBooklet(0,0)
            else :
                self.presetBooklet(0,1)
            self.guiPresetsShow("booklet")


        elif self.arw["radiopreset2"].get_active() == 1 :    # Multiple booklets
            if presetOrientation_i == 1 :
                self.presetBooklet(5,0)
            else :
                self.presetBooklet(5,1)
            self.guiPresetsShow("booklet")

        elif self.arw["radiopreset3"].get_active() == 1 :    # 2-up
            if presetOrientation_i == 1 :
                self.presetUp(1,2)
            else :
                self.presetUp(2,1)
            self.guiPresetsShow("copies")

        elif self.arw["radiopreset4"].get_active() == 1 :   # 4-up in lines
            if presetOrientation_i == 1 :
                self.presetUp(2,2,1)
            else :
                self.presetUp(2,2,1)
            self.guiPresetsShow("")

        elif self.arw["radiopreset5"].get_active() == 1 :   # 4-up in columns
            if presetOrientation_i == 1 :
                self.presetUp(2,2,2)
            else :
                self.presetUp(2,2,2)
            self.guiPresetsShow("")

        elif self.arw["radiopreset6"].get_active() == 1 :   # x copies
            if presetOrientation_i == 1 :
                self.presetCopies(1,2)
            else :
                self.presetCopies(2,1)
            self.guiPresetsShow("copies")

        elif self.arw["radiopreset7"].get_active() == 1 :  # 1 page
            if presetOrientation_i == 1 :
                self.presetMerge()
            else :
                self.presetMerge()
            self.guiPresetsShow("copies")

        elif self.arw["radiopreset8"].get_active() == 1 :   # User defined
            return      # This button launchs the function "user_defined" which will handle the request


        preview_b = True
        if radiobutton != 0 and startup_b == False :
            self.preview(self.previewPage)

    def user_defined(self, widget) :
        # Called by the "user defined" option button in the main window
        # Gets data from the dialog where user enters the user defined layout
        # Process the text from the TextView and sets controls and variables
        # then update the preview.
        # @widget : if this parameter is False, the dialog is not shown (used by OpenProject)

        global startup_b, project_b, preview_b

        preview_b = False   # prevent multiple preview commands due to signals emitted by controls
        if widget == False :
            response = 1
        else :
            dialog = self.arw["dialog2"]
            response = dialog.run()
            dialog.hide()

        if response == 0 :
            return
        if response == 1 :
            buf = self.arw["user_layout"].get_buffer()
            start, end  = buf.get_bounds()
            layout_s = buf.get_text(start, end, False)

            imposition2 = ini.parse_user_layout(layout_s)

            self.userpages = imposition2[0]
            (self.imposition, numrows, numcols, pages) = imposition2

            # set step
            if self.arw["step_defined"].get_active() == True :
                step_s = self.arw["step_value"].get_text()
                self.presetCopies(numrows,numcols,step_s)
                self.guiPresetsShow("copies")
            else :
                self.presetUp(numrows,numcols)
                self.guiPresetsShow("")

            total_pages = numrows * numcols
            if len(pages) != total_pages :
                message =_("Expected page number was : %d. Only %d found. \nThere is an error in your layout, please correct") % (total_pages, len(pages))
                alert(message)



            # Update gui before updating preview, since preview takes its data from the gui
            while Gtk.events_pending():
                        Gtk.main_iteration()

            preview_b = True
            if startup_b == False :
                self.preview(self.previewPage)

    def select_step_value(self, widget, event) :
        # launched when user types something in "step_value" entry
        # Selects the appropriate radio button
        self.arw["step_defined"].set_active(True)

    def guiPresetsShow(self, action_s) :

        StepWidgets = [self.arw["label15"], self.arw["entry15"]]
        LeafsWidgets = [self.arw["label13"], self.arw["entry13"]]
        OrientationWidgets = [self.arw["label11"], self.arw["presetOrientation1"],
                              self.arw["label12"], self.arw["presetOrientation2"]]


        for a in StepWidgets + LeafsWidgets + OrientationWidgets :
            a.hide()

        if action_s == "booklet" :
            for a in LeafsWidgets + OrientationWidgets :
                a.show()

        if action_s == "copies" :
            for a in StepWidgets + OrientationWidgets :
                a.show()


    def presetBooklet(self, leafs_i, orientation) :
        if orientation == 0 :
            self.arw["entry11"].set_value(1)                    # rows
            self.arw["entry12"].set_value(2)                    # columns
        else :
            self.arw["entry11"].set_value(2)                    # rows
            self.arw["entry12"].set_value(1)                    # columns
        self.arw["entry13"].set_text(str(leafs_i))              # leafs in booklet
        ini.repeat = 0
        ini.booklet = 1

    def presetUp(self, r,c,l=1) :
        self.arw["entry11"].set_value(r)                    # rows
        self.arw["entry12"].set_value(c)                    # columns
        ini.booklet = 0                          # checkerboard
        ini.radioDisp = l                        # lines / columns
        ini.repeat = 0

    def presetCopies(self, r,c, step="1") :
        global step_i
        self.arw["entry11"].set_value(r)                    # rows
        self.arw["entry12"].set_value(c)                    # columns
        ini.booklet = 0                          # checkerboard
        ini.repeat = 1
        self.arw["entry15"].set_text(step)                   # step

    def presetCopies2(self, r,c,l=1) :
        self.arw["entry11"].set_value(r)                    # rows
        self.arw["entry12"].set_value(c)                    # columns
        ini.booklet = 0                          # checkerboard
        ini.radioDisp = l                        # lines / columns
        ini.repeat = 0


    def presetMerge(self) :                     # One page
        global outputFile, inputFile_a

        self.arw["entry11"].set_value(1)                    # rows
        self.arw["entry12"].set_value(1)                    # columns
        ini.booklet = 0                          # checkerboard
        ini.repeat = 0

    def _________________________PREVIEW() :
        pass

    def OnDraw(self, area, cr):
        global page
        global areaAllocationW_i, areaAllocationH_i
        global previewColPos_a, previewRowPos_a, pageContent_a
        global refPageSize_a, numPages
        global outputStream, outputStream_mem

        # Was the size changed ?

        if (areaAllocationW_i == self.area.get_allocated_width()
            and areaAllocationH_i  == self.area.get_allocated_height()) :
                nochange_b = True
        else :
            nochange_b = False
            areaAllocationW_i = self.area.get_allocated_width()
            areaAllocationH_i  = self.area.get_allocated_height()


        # If nothing has not yet been loaded, show nofile.pdf
        try :
            data1 = outputStream.getvalue()
        except :
            f1 = open(sfp2("data/nofile.pdf"),"rb")
            data1 = f1.read()
            f1.close()


        # TODO : When two calls are too close, it may create a strange stack resulting in an empty OutputStream
        #        This has to be redesigned.
        if len(data1) == 0 :
            #print(".........  OutputStream is empty")
            return

        # If the preview has not changed, don't render
        # TODO : This must be redesigned, because with the image in memory, it may be necessary to render again with the same data
##        if data1 == outputStream_mem :
##            return
##        else :
##            outputStream_mem = data1

        try :
            bytes_data = GLib.Bytes(data1)
            input_stream = Gio.MemoryInputStream.new_from_bytes(bytes_data)
            # Take care that you need to call .close() on the Gio.MemoryInputStream once you're done with your pdf document.
            document = Poppler.Document.new_from_stream(input_stream, -1, None, None)
            self.document= document

        except :
            # bug workaround
            """
            Hi there, I installed the deb on Ubuntu 19.04. It installed fine but gives this message:

            Traceback (most recent call last):
               File "/usr/local/lib/python3.7/dist-packages/pdfbooklet/pdfbooklet.py", line 2342, in OnDraw
                document = Poppler.Document.new_from_stream(input_stream, -1, None, None)
             gi.repository.GLib.GError: poppler-quark: Failed to load document (0)

            The preview panel remains blank but when I press the Go button the .bklt file is produced correctly. I get the same if I download the tar and install manually. Also the same with many different input pdf files.
            """
            try:
                filepath = os.path.join(tempfile.gettempdir(), "preview.pdf")
                with open(filepath, "wb") as f1:
                    f1.write(data1)
                document = Poppler.Document.new_from_file("file:///" + filepath, None)
                self.document= document
            except:
                print("Error rendering document in poppler")
                printExcept()
                return False

        page = document.get_page(0)
        self.page = page

        x = document.get_n_pages()
        if x > 1 :
            self.page1 = document.get_page(1)
        else :
            self.page1 = None

        # calculate the preview size

        pix_w, pix_h = page.get_size()

        A = int((areaAllocationH_i * pix_w) / pix_h)    # width of preview if full height
        B = int((areaAllocationW_i * pix_h) / pix_w)    # height of preview if full width


        if A < areaAllocationW_i :                      # if full height is OK
            heightPoints = areaAllocationH_i
            widthPoints = A
            scale = areaAllocationH_i / pix_h
        else :
            widthPoints = areaAllocationW_i
            heightPoints = B
            scale = areaAllocationW_i / pix_w

        self.preview_scale = scale          # this will be needed for drag (see the end_drag function)
        Hoffset_i = int((areaAllocationW_i - widthPoints) /2)
        Voffset_i = int((areaAllocationH_i - heightPoints)/2)

        # set background.
        cr.set_source_rgb(0.7, 0.6, 0.5)
        cr.paint()

        # set page background
        cr.set_source_rgb(1, 1, 1)
        cr.rectangle(Hoffset_i, Voffset_i, widthPoints, heightPoints)

        cr.fill()

        # render page

        cr.save()
        cr.translate(Hoffset_i, Voffset_i)
        cr.scale(scale,scale)
        self.page.render(cr)
        cr.restore()

        # If there are two pages in the preview, the second will be printed over the other
        # to help fine tuning the margins for duplex printing

        if self.page1 :
            cr.save()
            cr.translate(Hoffset_i, Voffset_i)
            cr.scale(scale,scale)
            self.page1.render(cr)
            cr.restore()

        # clear memory
        input_stream.close()


        # draw middle line

        if ((ini.booklet > 0 and len(inputFiles_a) > 0)
            or self.arw["draw_middle_line"].get_active() == True) :
            cr.save()
            cr.set_line_width(1)
            cr.set_dash((10,8))
            cr.set_source_rgb(0.5, 0.5, 1)
            line_position = (areaAllocationW_i/2) + 0.5         # The reason for + 0.5 is explained here : https://www.cairographics.org/FAQ/#sharp_lines
                                                                # under the title : How do I draw a sharp, single-pixel-wide line?
            cr.move_to(line_position, Voffset_i)
            cr.line_to(line_position, (areaAllocationH_i - Voffset_i ))
            cr.stroke()
            cr.restore()


        # if the output is turned, swap the numbers of rows and columns
        if app.arw["globalRotation90"].get_active() == 1 \
          or  app.arw["globalRotation270"].get_active() == 1:
            preview_cols = rows_i
            preview_rows = columns_i
        else :
            preview_cols = columns_i
            preview_rows = rows_i


        columnWidth = widthPoints / preview_cols
        rowHeight = heightPoints / preview_rows

        #store position of columns and rows
        # previewColPos_a will contain the position of the left of all columns (from left to right)
        # previewRowPos_a will contain the position of the top of all rows (from bottom to top)
        previewColPos_a = []
        previewRowPos_a = []
        previewPagePos_a = {}


        for a1 in range(preview_cols) :
            #left of the column
            previewColPos_a += [int((a1 * columnWidth) + Hoffset_i)]
        for a2 in range(preview_rows) :
            #top of the row
            # rows count starts from bottom => areaAllocationH_i - ...
            previewRowPos_a += [areaAllocationH_i - (int((a2 * rowHeight) + Voffset_i ))]
        # add the right pos of the last col
        previewColPos_a += [int(previewColPos_a[a1] + columnWidth)]
        previewRowPos_a += [int(previewRowPos_a[a2] - rowHeight)]


        # show page numbers
        i = 0
        pageContent_a = {}


        for a in self.rotate_layout() :     # returns the layout, rotated if necessary

            # human readable page number
            pageRef_s = self.ar_pages[0][i]
            file_number, page_number = pageRef_s.split(":")
            page_number = int(page_number) + 1
            if file_number == "1" :
                pageNumber = str(page_number)
            else :
                pageNumber = str(file_number) + ":" + str(page_number)


            fontsize_i = int(columnWidth / 4)
            if fontsize_i < 10 :
                fontsize_i = 10
            colpos_i = previewColPos_a[a[1]] + (columnWidth / 2)
            rowpos_i = previewRowPos_a[a[0]] - (rowHeight / 2)
            x1,x2 = colpos_i,rowpos_i

            # draw page number
            if self.arw["hide_numbers"].get_active() == 0 :
                # TODO cr.select_font_face("Georgia",
    ##                cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
    ##            AttributeError: 'gi.repository.cairo' object has no attribute 'FONT_SLANT_NORMAL'
    ##            Remplacé par 0 et 1 ci-dessous
                cr.select_font_face("Georgia", 0, 1)
                cr.set_font_size(fontsize_i)
                x_bearing, y_bearing, txtWidth, txtHeight = cr.text_extents(pageNumber)[:4]

                colpos_i -= int(txtWidth / 4)
                rowpos_i -= int(txtHeight / 4)


                cr.move_to(colpos_i, rowpos_i - y_bearing)
                cr.set_source_rgba(1, 0, 0, 0.6)
                cr.show_text(pageNumber)

            pageId = str(a[0]) + ":" + str(a[1])
            pageContent_a[pageId] = [file_number, page_number]

            # page position
            bottom = previewRowPos_a[a[0]]
            top = previewRowPos_a[a[0]] - rowHeight
            left = previewColPos_a[a[1]]
            right = previewColPos_a[a[1]] + columnWidth



            # draw rectangle if selected
            humanReadableRow_i = (preview_rows - a[0])
            pagePosition_a = [humanReadableRow_i, a[1] + 1]
            pagePosition_s = self.rotate_position(pagePosition_a)
            pageNumber_s = str(file_number) + ":" + str(page_number)
            draw_page_b = False
            type_s = ""

            # is this page odd or even ?
            if self.arw["evenpages"].get_active() == 1 :
                if int(pageNumber) % 2 == 0 :
                    cr.set_source_rgb(0, 0, 1)
                    draw_page_b = True
                    type_s = "even"

            elif self.arw["oddpages"].get_active() == 1 :
                if int(pageNumber) % 2 == 1 :
                    cr.set_source_rgb(0, 0, 1)
                    draw_page_b = True
                    type_s = "odd"

            # if page is selected
            elif pagePosition_s in selectedIndex_a :
                # Select the rectangle color
                cr.set_source_rgb(1, 0, 0)
                draw_page_b = True
                type_s = "select"


            elif pageNumber_s in selectedIndex_a :
                # Select the rectangle color
                cr.set_source_rgb(0, 0.7, 0)
                draw_page_b = True
                type_s = "select"




            # draw rectangle
            if draw_page_b == True :

                file_number, page_number = self.myselectedpage.split(":")
                selected_ref = file_number + ":" + str(int(page_number) - 1)


                coord = [left, top]
                cr.set_line_width(3)
                cr.rectangle(coord[0], coord[1],  columnWidth, rowHeight)
                cr.stroke()


            i += 1


        # Show page size and total pages number
##        cr.select_font_face("Arial", 0, 1)
            # TODO Anciennement cr.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
            # mais les attributs ne marche plus.
##        cr.set_font_size(12)
        try :
            message = str(round(refPageSize_a[0] * adobe_l))
            message += " x " + str(round(refPageSize_a[1] * adobe_l))
            message += " - " + str(numPages) + " pages"
##            x_bearing, y_bearing, txtWidth, txtHeight = cr.text_extents(message)[:4]
##            cr.move_to(20,20)
##            cr.set_source_rgb(0, 0.6, 0)
##            cr.show_text(message)
            self.arw["info_fichier"].set_text(message)
        except :
            pass        # if refPageSize is not defined, error



    def rotate_layout(self) :
        rotated_layout= []
        for i in range (len(self.ar_layout)) :
            r, c = self.ar_layout[i]

            # Flip columns or rows if vertical or horizontal flip is selected
            if app.arw["vflip2"].get_active() == 1 :
                r = (rows_i - 1) - r
            if app.arw["hflip2"].get_active() == 1 :
                c = (columns_i - 1) - c

            # if output page is rotated (global rotation)
            if app.arw["globalRotation270"].get_active() == 1 :
                # invert row; We use columns_i because when rotated 90°,
                # the numbers of rows of the preview is the number of columns of the page
                r = (rows_i - 1) - r
                r,c = c,r       # swap

            elif app.arw["globalRotation90"].get_active() == 1 :
                # invert column; ; We use rows_i because when rotated 90°,
                # the numbers of columns of the preview is the number of rows of the page
                c = (columns_i - 1) - c
                r,c = c,r       # swap

            elif app.arw["globalRotation180"].get_active() == 1 :
                # invert row and column
                r = (rows_i - 1) - r
                c = (columns_i - 1) - c


            rotated_layout.append([r,c])
        return rotated_layout



    def rotate_position(self, position) :

##        print( "source : ", position)
        r, c = position
        # if output page is rotated (global rotation)
        if app.arw["globalRotation270"].get_active() == 1 :
            # invert row; We use columns_i because when rotated 90°,
            # the numbers of rows of the preview is the number of columns of the page
            r = (columns_i + 1) - r
            r,c = c,r       # swap

        elif app.arw["globalRotation90"].get_active() == 1 :
            # invert column; ; We use rows_i because when rotated 90°,
            # the numbers of columns of the preview is the number of rows of the page
            c = (rows_i + 1) - c
            r,c = c,r       # swap

        elif app.arw["globalRotation180"].get_active() == 1 :
            # invert row and column
            r = (rows_i + 1) - r
            c = (columns_i  +1) - c

##        print ("dest : ", str(r) + "," + str(c))

        return str(r) + "," + str(c)



    def selectPage (self, widget, event=None):
        # Called by a click on the preview or on the radio buttons, this function will :
        #   - Select the appropriate Id
        #   - launch area_expose to update the display
        #   - fill in the gtkEntry widgets which contains the transformations
        #
        # The selected_page list contains a list of six values :
        #   - row and column (Pdf format)
        #   - file number and page number
        #   - row and column if the output page is rotated

        global previewColPos_a, previewRowPos_a, canvasId20, pageContent_a
        global selectedIndex_a, selected_page, selected_pages_a, pageId


        if event == None :                  # Click on a radio button
            if self.thispage.get_active() == 1 :
                pageId = str(selected_page[2]) + ":" + str(selected_page[3])
                Id = pageId
            elif self.evenpages.get_active() == 1 :
                Id = "even"
            elif self.oddpages.get_active() == 1 :
                Id = "odd"
            else :         # first button, pages in this position
                humanReadableRow_i = rows_i - selected_page[4]
                Id = str(str(humanReadableRow_i) + "," + str(selected_page[5] + 1))
                Id = str(str(selected_page[5] + 1) + "," + str(humanReadableRow_i))

        elif event.type == Gdk.EventType.BUTTON_PRESS:
            if event.button == 3 :            # right click, runs the context menu
                self.arw["contextmenu1"].popup(None, None, None, None, event.button, event.time)
                return

            else :
                # get preview area
                left_limit = previewColPos_a[0]
                bottom_limit = previewRowPos_a[-1]
                right_limit = previewColPos_a[-1]
                top_limit = previewRowPos_a[0]

                xpos = event.x
                ypos = event.y

                self.initdrag = [xpos, ypos]        # Will be used for moving page with the mouse
                self.preview_limits = [left_limit, right_limit, top_limit, bottom_limit]

                # check if click is inside preview
                if (xpos < left_limit
                    or xpos > right_limit
                    or ypos < bottom_limit
                    or ypos > top_limit) :

                        return

                # find the row and column
                for c in range(len(previewColPos_a)) :
                    if xpos > previewColPos_a[c] and xpos < previewColPos_a[c + 1]:
                        leftPos_i = previewColPos_a[c]
                        rightPos_i = previewColPos_a[c + 1]
                        break


                for r in range(len(previewRowPos_a)) :
                    if ypos < previewRowPos_a[r] and ypos > previewRowPos_a[r + 1]:
                        bottomPos_i = previewRowPos_a[r]
                        topPos_i = previewRowPos_a[r + 1]
                        break

                r1 = r
                c1 = c

                # it should be possible to use rotate_layout

                # Flip columns or rows if vertical or horizontal flip is selected
##                if app.arw["vflip2"].get_active() == 1 :
##                    r1 = (rows_i - 1) - r
##                if app.arw["hflip2"].get_active() == 1 :
##                    c1 = (columns_i - 1) - c

                # if output page is rotated (global rotation)
                if app.arw["globalRotation90"].get_active() == 1 :
                    # invert row; We use columns_i because when rotated 90°,
                    # the numbers of rows of the preview is the number of columns of the page
                    r1 = (columns_i - 1) - r1
                    r1,c1 = c1,r1       # swap

                elif app.arw["globalRotation270"].get_active() == 1 :
                    # invert column; ; We use rows_i because when rotated 90°,
                    # the numbers of columns of the preview is the number of rows of the page
                    c1 = (rows_i - 1) - c1
                    r1,c1 = c1,r1       # swap

                elif app.arw["globalRotation180"].get_active() == 1 :
                    # invert row and column
                    r1 = (rows_i - 1) - r1
                    c1 = (columns_i - 1) - c1

                selected_page = [r, c]
                selected_page += pageContent_a[str(r)  + ":"  + str(c)]
                selected_page += [r1, c1]


                # Selection information
                self.myselectedpage = str(selected_page[2]) + ":" + str(selected_page[3])
                if self.thispage.get_active() == 1 :        # This page
                    Id = self.myselectedpage
                elif self.evenpages.get_active() == 1 :     # even pages
                    Id = "even"
                elif self.oddpages.get_active() == 1 :      # odd pages
                    Id = "odd"
                else :                                      # first button, pages in this position
                    humanReadableRow_i = rows_i - selected_page[4]
                    Id = str(str(humanReadableRow_i) + "," + str(selected_page[5] + 1))



                if event.state != Gdk.ModifierType.CONTROL_MASK :        # If Control not pressed, delete previous selection
                    selectedIndex_a = {}
                    selected_pages_a = []

                # position reference
                selectedIndex_a[Id] = 1
                selected_pages_a.append(selected_page)


                # force the "draw" signal to update the display
                self.arw["drawingarea1"].hide()
                self.arw["drawingarea1"].show()






        else :              # unsupported event
            return

        # Load settings in transformation dialogs
        if event.state != Gdk.ModifierType.CONTROL_MASK :       # but only if CONTROL is not pressed
            self.load_settings_in_dialog(Id)


    def load_settings_in_dialog(self, Id) :


            # defaults
            self.arw["htranslate1"].set_text("0")
            self.Vtranslate1.set_text("0")
            self.scale1.set_text("100")
            self.rotation1.set_text("0")
            self.arw["xscale1"].set_text("100")
            self.arw["yscale1"].set_text("100")
            self.arw["vflip1"].set_active(False)
            self.arw["hflip1"].set_active(False)


            if Id in config :
                if "htranslate" in config[Id] :
                    self.arw["htranslate1"].set_text(str(config[Id]["htranslate"]))
                if "vtranslate" in config[Id] :
                    self.Vtranslate1.set_text(str(config[Id]["vtranslate"]))
                if "scale" in config[Id] :
                    self.scale1.set_text(str(config[Id]["scale"]))
                if "rotate" in config[Id] :
                    self.rotation1.set_text(str(config[Id]["rotate"]))
                if "xscale" in config[Id] :
                    self.arw["xscale1"].set_text(str(config[Id]["xscale"]))
                if "yscale" in config[Id] :
                    self.arw["yscale1"].set_text(str(config[Id]["yscale"]))

                if "vflip" in config[Id] :
                    bool_b = config[Id]["vflip"]
                    if bool_b == "True" or bool_b == True or bool_b == "1" :           # when parameter comes from the ini file, it is a string
                        bool_b = 1
                    elif bool_b == "False" or bool_b == False or bool_b == "0" :
                        bool_b = 0
                    self.arw["vflip1"].set_active(bool_b)
                if "hflip" in config[Id] :
                    bool_b = config[Id]["hflip"]
                    if bool_b == "True" or bool_b == True or bool_b == "1" :           # when parameter comes from the ini file, it is a string
                        bool_b = 1
                    elif bool_b == "False" or bool_b == False or bool_b == "0" :
                        bool_b = 0

                    self.arw["hflip1"].set_active(bool_b)


    def drag_motion(self, widget, cr, x, y, time) :
        # unused
        # To activate, connect the signal "drag-motion" of then eventbox parent of drawingarea1
        # to this function
        print (x)
        cr.set_line_width(1)
        cr.rectangle(x, y,  2, 2)
        cr.stroke()
        return False


    def set_even_odd_settings(self, widget) :    # launched by a clic on the domain buttons
        global selected_page, selectedIndex_a
        #print ("set even odd")
        if self.evenpages.get_active() == 1 :
            Id = "even"
            selected_page = Id
            self.load_settings_in_dialog(Id)
        elif self.oddpages.get_active() == 1 :
            Id = "odd"
            selected_page = Id
            self.load_settings_in_dialog(Id)
        else :
            #print ("reset selection")
            selected_page = None
            selectedIndex_a = {}
        self.previewUpdate()



    def end_drag(self, widget, event) :
        global outputScale

        # Thisfunction will move the page when the mouse button is released
        x = event.x
        y = event.y

        # scaling factor
        if self.arw["autoscale"].get_active() == 1 :
            scaling_factor = self.preview_scale * outputScale
        else :
            scaling_factor = self.preview_scale


        scaling_factor2 = scaling_factor / adobe_l

        hmove = ((x - self.initdrag[0]) / scaling_factor2)
        vmove = ((y - self.initdrag[1]) / scaling_factor2)

        if self.arw["move_with_mouse"].get_active() == 1 :

            # Calculate the scaling factor

            temp = self.arw["htranslate1"].get_text()
            temp = temp.replace(",", ".")
            temp = float(temp)
            temp += hmove
            temp = str(temp).split(".")
            temp = temp[0] + "." + temp[1][0:1]
            self.arw["htranslate1"].set_text(temp)
            self.transformationsApply("")

            temp = self.arw["vtranslate1"].get_text()
            temp = temp.replace(",", ".")
            temp = float(temp)
            temp -= vmove
            temp = str(temp).split(".")
            temp = temp[0] + "." + temp[1][0:1]
            self.arw["vtranslate1"].set_text(temp)
            self.transformationsApply("")

        elif self.arw["delete_rectangle"].get_active() == 1 :
            # preview_limits gives : left x,  right x, bottom y, top y, in pixels.
            #
            # init_drag gives : x, y
            # x = horizontal position
            left = self.preview_limits[0] # left margin
            top = self.preview_limits[3] # top margin


            x1 = (self.initdrag[0] - left)       # start drag
            y1 = self.initdrag[1]
            y1 = self.preview_limits[2] - y1        # invert the vertical position, because Pdf counts from bottom
                                                    # This corrects also the top margin because bottom y = page height + margin
            x2 = x - left        # end drag
            y2 = y
            y2 = self.preview_limits[2] - y2
            width = x2 - x1
            height = y2 - y1
            x1 = x1 / scaling_factor
            y1 = y1 / scaling_factor
            width = width / scaling_factor
            height = height / scaling_factor
            ini.delete_rectangle = [x1,y1,width,height]

        elif self.arw["divide"].get_active() == 1 :
            # preview_limits gives : left x,  right x, bottom y, top y, in pixels.
            #
            # init_drag gives : x, y
            # x = horizontal position
            left = self.preview_limits[0] # left margin
            top = self.preview_limits[3] # top margin


            x1 = (self.initdrag[0] - left)       # start drag
            y1 = self.initdrag[1]
            y1 = self.preview_limits[2] - y1        # invert the vertical position, because Pdf counts from bottom
                                                    # This corrects also the top margin because bottom y = page height + margin
            x2 = x - left        # end drag
            y2 = y
            y2 = self.preview_limits[2] - y2
            width = x2 - x1
            height = y2 - y1
            x1 = x1 / scaling_factor
            y1 = y1 / scaling_factor
            width = width / scaling_factor
            height = height / scaling_factor
            ini.delete_rectangle = [x1,y1,width,height]



    def createSelection(self) :
        global inputFiles_a
        i = 1
        x = []
        for f in inputFiles_a :
            fileName = inputFiles_a[f]
            numPages = inputFile_a[fileName].getNumPages()
            for z in range(numPages) :
                pageRef_s = str(i) + ":" + str(z)
                if pageRef_s + "deleted" in deletedIndex_a :
                    if deletedIndex_a[pageRef_s + "deleted"] == 1 :
                        pass
                    else :
                        x += [pageRef_s]
                else :
                    x += [pageRef_s]
            i += 1
        self.selection_s = self.compressSelection(x)

    def compressSelection(self, x) :
        i = 0
        temp = {}
        out = ""
        for a in x :
            b = a.split(":")
            if len(b) == 1 :
                npage = b[0]
                nfile = 1
            else :
                npage = b[1]
                nfile = b[0]

            if i == 0 :
                temp["file"] = nfile
                temp["first"] = npage
                temp["last"] = npage
            else :
                if nfile == temp["file"] and int(npage) == int(temp["last"]) + 1  :
                    temp["last"] = npage            # on continue
                else :                              # sinon on écrit
                    if temp["first"] == "-1":
                        out += "b"
                    else :
                        out += str(temp["file"]) + ":" + str(temp["first"])
                        if temp["last"] != temp["first"] :
                            out += "-" + str(temp["last"])
                    out += "; "
                    # et on mémorise
                    temp["file"] = nfile
                    temp["first"] = npage
                    temp["last"] = npage
            i += 1

        if temp["first"] == "-1":
            out += "b"
        else :
            out += str(temp["file"]) + ":" + str(temp["first"])
        if temp["last"] != temp["first"] :
            out += "-" + str(temp["last"])

        # compress blank pages
        temp1 = out.split(";")
        out = ""
        blank_count = 0
        for a in temp1 :
            if a.strip() == "b" :
                blank_count += 1
            else :
                if blank_count > 0 :
                    out += str(blank_count) + "b;"
                    blank_count = 0
                out += a + ";"
        if blank_count > 0 :
            out += str(blank_count) + "b"

        return out

    def edit_selection(self, widget) :
        dialog = self.arw["dialog1"]
        TextBuffer = self.arw["textview1"].get_buffer()
        selection1 = self.selection_s.replace(";","\n")
        TextBuffer.set_text(selection1)
        choice = dialog.run()
        if choice == 1 :
            start_iter = TextBuffer.get_start_iter()
            end_iter = TextBuffer.get_end_iter()
            selection2 = TextBuffer.get_text(start_iter, end_iter, False)
            selection2 = selection2.replace("\n",";")
            self.selection_s = selection2
            if self.shuffler:
                self.shuffler.model.clear()
                self.shuffler.pdfqueue = []
                self.shuffler.nfile = 0
                self.loadShuffler()
                # TODO : N'est à faire que si la liste des fichiers a changé
                self.shuffler.rendering_thread.pdfqueue = self.shuffler.pdfqueue
        dialog.hide()
        self.previewUpdate()


    def compare_files_selection(self,widget) :
        # Create a selection to display two or more files side by side

        # Number of files and pages
        files_data = {}
        index = 1
        maxPages = 0
        numFiles = len(inputFiles_a)
        for f in inputFiles_a :
            fileName = inputFiles_a[f]
            numPages = inputFile_a[fileName].getNumPages()
            if numPages > maxPages :
                maxPages = numPages
            files_data[index] = numPages
            index += 1

        # create the selection
        index = 1
        text = ""
        for i in range(maxPages) :
            temp = []
            for j in range(1, numFiles + 1) :
                if files_data[j] > i :              # if the file is shorter, insert blank pages
                    temp.append(str(j) + ":" + str(i))
                else :
                    temp.append("b")
            text += ",".join(temp) + "\n"

        TextBuffer = self.arw["textview1"].get_buffer()
        TextBuffer.set_text(text)


    def preview(self, previewPage, delete = 1) :


        global mediabox_l0, columns_i, rows_i, step_i, urx_i, ury_i, mediabox_l
        global outputScale, pageContent_a
        global selectedIndex_a, deletedIndex_a
        global areaAllocationW_i, areaAllocationH_i
        global preview_b


        if preview_b == False :
            return



        if self.readGui(0) :
            self.manage_backup()
            if self.render.parsePageSelection() :
                #self.readConditions()
                ar_pages, ar_layout, ar_cahiers = self.render.createPageLayout(0)
                if ar_pages != None :
                    if previewPage > len(ar_pages) - 1 :
                        previewPage = len(ar_pages) - 1
                        self.previewPage = previewPage
                    self.arw["previewEntry"].set_text(str(previewPage + 1))
                    mem = ar_pages[previewPage]
                    try :
                        mem1 = ar_pages[previewPage + 1]
                    except :                # If we are arrived at the last page
                        pass
                    ar_pages = {}
                    ar_pages[0] = mem

                    # If previewing of two pages one over the other is requested
                    if 1 == 2 :
                        ar_pages[1] = mem1

                    self.ar_pages = ar_pages
                    self.ar_layout = ar_layout

                    if self.render.createNewPdf(ar_pages, ar_layout, ar_cahiers, "", previewPage) :

                        # force the "draw" signal to update the display
                        self.arw["drawingarea1"].hide()
                        self.arw["drawingarea1"].show()



    def preview_keys(self, widget, event = None) :
        print ( "==========>>", event)

    def previewNext(self, dummy, event=None) :
        global selected_page, selectedIndex_a
        if event.state != Gdk.ModifierType.CONTROL_MASK :
            selected_page = None
            selectedIndex_a = {}
        self.previewPage += 1
        self.arw["previewEntry"].set_text(str(self.previewPage + 1))
        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview

    def previewPrevious(self, dummy, event = None) :
        global selected_page, selectedIndex_a
        if event.state != Gdk.ModifierType.CONTROL_MASK :
            selected_page = None
            selectedIndex_a = {}
        self.previewPage -= 1
        if self.previewPage < 0 :
            self.previewPage = 0
        self.arw["previewEntry"].set_text(str(self.previewPage + 1))
        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview

    def previewFirst(self, widget) :
        global selected_page, selectedIndex_a
        selected_page = None
        selectedIndex_a = {}
        self.previewPage = 0
        self.arw["previewEntry"].set_text(str(self.previewPage + 1))
        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview

    def previewLast(self, widget) :
        global selected_page, selectedIndex_a
        selected_page = None
        selectedIndex_a = {}
        self.previewPage = 1000000    # CreatePageLayout will substitute the right number
        self.arw["previewEntry"].set_text(str(self.previewPage + 1))
        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview

    def previewUpdate(self, Event = None, mydata = None) :
        global inputFiles_a

        if len(inputFiles_a) == 0 :
            #showwarning(_("No file loaded"), _("Please select a file first"))
            return False
        if Event != None :
            value_s = self.arw["entry11"].get_text()
            value2_s = self.arw["entry12"].get_text()
            if value_s != "" and value2_s != "" :
                previewPage = self.arw["previewEntry"].get_text()
                if previewPage != "" :
                    self.preview(int(previewPage) - 1)
                return
            else :
                return
##        self.preview(self.previewPage)
        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview




    def previewDelayedUpdate(self, event) :     # Unused : does not work
        return
        print("previewUpdateDelayed")
        try :
            self.t1.cancel()
        except :
            pass
        self.t1 = threading.Timer(1, self.previewUpdate, [event])
        self.t1.start()
        print("timer démarré")

    def preview2(self, widget) :
        global selected_page
        selected_page = None
        previewPage = int(widget.get_text())
        self.previewPage = previewPage - 1
        self.arw["previewEntry"].set_text(str(self.previewPage + 1))
        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview

    def manage_backup(self) :
        return
        if self.backup_command == False :
            self.backup_command = True
            return
        if len(self.backup) == 0 :
            self.backup.append(copy.deepcopy(config))
            self.backup_index = len(self.backup)
        else :
            last = self.backup[-1]
            a = repr(config)
            b = repr(last)
            if a == b :
                return
            else :
                self.backup.append(copy.deepcopy(config))
                #print (len(self.backup))
                self.backup_index = len(self.backup)

    def go_back(self, widget) :
        config = copy.deepcopy(self.backup[self.backup_index - 2])
        self.setupGui()
        self.backup_command = False

    def _____________________SHUFFLER() :
        pass


    def runPS(self, widget=None) :
        global inputFiles_a, pagesSel

        if len(inputFiles_a) == 0 :
            showwarning(_("No selection"), _("There is no selected file. \nPlease select a file first. "))
            return
        if self.shuffler == None :
            self.shuffler = PdfShuffler()
            self.shuffler_window = self.shuffler.uiXML.get_object('main_window')
            self.shuffler.uiXML.get_object('menubar').hide()
            self.shuffler.uiXML.get_object('toolbar1').hide()
            #self.shuffler.uiXML.get_object('menu1_RR').hide()
            #self.shuffler.uiXML.get_object('menu1_RL').hide()
            self.shuffler.uiXML.get_object('menu1_crop').hide()
            self.shuffler.window.set_deletable(False)
            shufflerBB = self.arw['shufflerbuttonbox']
            shufflerBB.unparent()
            vbox = self.shuffler.uiXML.get_object('vbox1')
            vbox.pack_start(shufflerBB, False, True, 0)

            self.loadShuffler()

        else :
            self.shuffler_window.show()


    def loadShuffler(self) :
            render.parsePageSelection("", 0)

            for key in inputFiles_a :
                pdfdoc = PDF_Doc(inputFiles_a[key], self.shuffler.nfile, self.shuffler.tmp_dir)
                if pdfdoc.nfile != 0 and pdfdoc != []:
                    self.shuffler.nfile = pdfdoc.nfile
                    self.shuffler.pdfqueue.append(pdfdoc)

            angle=0
            crop=[0.,0.,0.,0.]
            for page in pagesSel :
                file1, page1 = page.split(":")
                npage = int(page1) + 1
                filenumber = int(file1) - 1
                pdfdoc = self.shuffler.pdfqueue[filenumber]
                if npage > 0 :
                    docPage = pdfdoc.document.get_page(npage-1)
                else :
                    docPage = pdfdoc.document.get_page(0)
                w, h = docPage.get_size()

                # blank page
                if npage == 0 :
                    descriptor = 'Blank'
                    width = self.shuffler.iv_col_width
                    row =(descriptor,         # 0
                          None,               # 1
                          1,                  # 2
                          -1,                 # 3
                          self.zoom_scale,    # 4
                          "",                 # 5
                          0,                  # 6
                          0,0,                # 7-8
                          0,0,                # 9-10
                          w,h,                # 11-12
                          2.              )  # 13 FIXME

                    self.shuffler.model.append(row)
                else :





                    descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
                    iter = self.shuffler.model.append((descriptor,         # 0
                                              None,               # 1
                                              pdfdoc.nfile,       # 2
                                              npage,              # 3
                                              self.shuffler.zoom_scale,    # 4
                                              pdfdoc.filename,    # 5
                                              angle,              # 6
                                              crop[0],crop[1],    # 7-8
                                              crop[2],crop[3],    # 9-10
                                              w,h,                # 11-12
                                              2.              ))  # 13 FIXME
                    self.shuffler.update_geometry(iter)
                    res = True

            self.shuffler.reset_iv_width()
            if res:
                self.shuffler.render()
            return res



    def closePS(self) :
        if self.shuffler :
            self.shuffler.rendering_thread.quit = True
            Gdk.threads_enter()
            # TODO ££
##            if self.shuffler.rendering_thread.paused == True:
##                 self.shuffler.rendering_thread.evnt.set()
##                 self.shuffler.rendering_thread.evnt.clear()
            self.shuffler_window.destroy()
            self.shuffler= None
            self.shuffler
            self.runPS()

    def getShufflerSel(self, widget):

        selection = []
        for row in self.shuffler.model:
            Id = str(row[2]) + ":" + str(row[3])
            selection += [Id]
            angle = row[7]

            if angle != 0 :
                if angle == 90 :        # In Pdf format, global rotation rotates clockwise,
                    angle = 270         # fine rotation (used by PdfBooklet) rotates counterclockwise.

                elif angle == -90 :
                    angle = 90


            if not Id in config and angle != 0 :
                config[Id] = {}
                # defaults
                config[Id]["htranslate"] = 0
                config[Id]["vtranslate"] = 0
                config[Id]["scale"] = 1
                config[Id]["rotate"] = 0

            if angle != 0 :
                config[Id]["shuffler_rotate"] = angle
##                if angle == 270 :
##                    config[Id]["vtranslate"] = pix_w
##                elif angle == 90 :
##                    config[Id]["htranslate"] = pix_h





        self.selection_s = self.compressSelection(selection)
        self.shuffler_window.hide()
        self.previewUpdate()


    def closeShuffler(self, widget) :
        self.shuffler_window.hide()


    def ________________TRANSFORMATIONS() :
        pass


    def ta2(self, widget, event = "") :
        print("ta2", event)
        self.transformationsApply("")

    def transformationsApply(self, widget, event="", force_update = False) :
        # Reads the values in the gui and updates the config dictionary

        global selected_page, selected_pages_a, rows_i

        for this_selected_page in selected_pages_a :

            if this_selected_page == None :
                showwarning(_("No selection"), _("There is no selected page. \nPlease select a page first. "))
                return
            # this_selected_page 4 and 5 contain the correct page reference, including the global rotation.
            humanReadableRow_i = rows_i - this_selected_page[4]
            Id = str(str(humanReadableRow_i) + "," + str(this_selected_page[5] + 1))

            pageId = str(this_selected_page[2]) + ":" + str(this_selected_page[3])

            # if transformation is for this page only, use page ref instead of position ref
            if self.thispage.get_active() == 1 :
                Id = pageId
            if self.evenpages.get_active() == 1 :
                Id = "even"
            if self.oddpages.get_active() == 1 :
                Id = "odd"

            config[Id] = {}

            config[Id]["htranslate"] = self.arw["htranslate1"].get_text()     # data from the gui unmodified
            config[Id]["vtranslate"] = self.Vtranslate1.get_text()
            config[Id]["scale"] = self.scale1.get_text()
            config[Id]["rotate"] = self.rotation1.get_text()
            config[Id]["xscale"] = self.arw["xscale1"].get_text()
            config[Id]["yscale"] = self.arw["yscale1"].get_text()
            config[Id]["vflip"] = self.arw["vflip1"].get_active()
            config[Id]["hflip"] = self.arw["hflip1"].get_active()
        #if not force_update :
            # prevent useless update. If no change, return.
            # TODO : à débuguer
##            a = repr(config[Id])
##            if not "memory1" in self :
##                memory1 = {}
##                b = self.memory1["memory1"]
##                if a == b :
##                    return
##            else :
##                self.memory1["memory2"] = 0
##            self.memory1["memory1"] = a
##            self.memory1["memory2"] += 1

        if self.arw["automaticUpdate"].get_active() == 0 :  # If automatic update is not disabled
            self.preview(self.previewPage)                # update the preview


    def resetTransformations(self, event = 0) :
        self.arw["htranslate1"].set_value(0)
        self.arw["vtranslate1"].set_value(0)
        self.arw["scale1"].set_value(100)
        self.arw["xscale1"].set_value(100)
        self.arw["yscale1"].set_value(100)
        self.arw["rotation1"].set_value(0)
        self.arw["vflip1"].set_active(False)
        self.arw["hflip1"].set_active(False)

        if event != 0 : self.transformationsApply("dummy", force_update = True)
        pass

    def resetTransformations2(self, event = 0) :
        # reset default values for global transformations
        self.arw["htranslate2"].set_value(0)
        self.arw["vtranslate2"].set_value(0)
        self.arw["scale2"].set_value(100)
        self.arw["xscale2"].set_value(100)
        self.arw["yscale2"].set_value(100)
        self.arw["rotation2"].set_value(0)
        self.arw["vflip2"].set_active(False)
        self.arw["hflip2"].set_active(False)
        if event != 0 :
            self.arw["globalRotation0"].set_active(1)
            self.previewUpdate()
        pass

    def copy_transformations(self, event) :          # called by context menu
        self.clipboard["htranslate1"] = self.arw["htranslate1"].get_text()     # data from the gui unmodified
        self.clipboard["vtranslate1"] = self.Vtranslate1.get_text()
        self.clipboard["scale1"] = self.scale1.get_text()
        self.clipboard["rotate1"] = self.rotation1.get_text()
        self.clipboard["this_page"] = self.thispage.get_active()
        self.clipboard["xscale1"] = self.arw["xscale1"].get_text()
        self.clipboard["yscale1"] = self.arw["yscale1"].get_text()
        self.clipboard["vflip1"]  = self.arw["vflip1"].get_active()
        self.clipboard["hflip1"]  = self.arw["hflip1"].get_active()

    def paste_transformations(self, event) :          # called by context menu
         self.arw["htranslate1"].set_text(self.clipboard["htranslate1"])
         self.Vtranslate1.set_text(self.clipboard["vtranslate1"])
         self.scale1.set_text(self.clipboard["scale1"])
         self.rotation1.set_text(self.clipboard["rotate1"])
         self.thispage.set_active(self.clipboard["this_page"])
         self.arw["xscale1"].set_text(self.clipboard["xscale1"])
         self.arw["yscale1"].set_text(self.clipboard["yscale1"])
         self.arw["vflip1"].set_active(self.clipboard["vflip1"])
         self.arw["hflip1"].set_active(self.clipboard["hflip1"])

         self.transformationsApply("", force_update = True)



    def version21(self, widget) :
        showwarning("Not yet implemented", "This feature will be implemented in version 2.2")

    def aboutPdfbooklet(self, widget) :
        self.arw["Pdf-Booklet"].show()

    def aboutdialog1_close(self, widget,event) :
        self.arw["Pdf-Booklet"].hide()

    def print2(self, string, cr=0) :        # no  longer used
        global editIniFile

        return
        editIniFile = 0
        enditer = self.text1.get_end_iter()
        self.text1.insert(enditer, string)
        if cr == 1 :
            self.text1.insert(enditer, chr(10))
        iter0 = self.text1.get_end_iter()
        self.arw["text1"].scroll_to_iter(iter0,0)
        #  TODO : bug
        # This command hangs the program (the interface remains blank) when :
        #   a file has been loaded
        #   The page selector has been used
        #   a second file is loaded.
        #while Gtk.events_pending():
        #            Gtk.main_iteration()

    def test(self,event, dummy = None) :
        print("test")

    def destroyWindow() :
        pass        # commande encore présente dans Glade, à supprimer

    def go(self, button, preview = -1) :

        if self.readGui() :
            if self.render.parsePageSelection() :
                self.readConditions()
                ar_pages, ar_layout, ar_cahiers = self.render.createPageLayout()
                if ar_pages != None :
                    # Verify that the output file may be written to

                    if app.arw["entry2"].get_text() == "" :
                        inputFile = inputFiles_a[1]
                        inputFile_name = os.path.splitext(inputFile)[0]
                        outputFile = inputFile_name + "-bklt.pdf"
                    else :
                        outputFile = app.arw["entry2"].get_text()
                        inputFile = inputFiles_a[1]
                        inputFile_wo_ext = os.path.splitext(inputFile)[0]
                        (inputFile_path, inputFile_name) = os.path.split(inputFile)
                        (inputFile_basename, inputFile_ext) = os.path.splitext(inputFile_name)
                        inputFile_path += "/"
                        inputFile_ext = inputFile_ext[1:]


                        outputFile = outputFile.replace("%F", inputFile_wo_ext)
                        outputFile = outputFile.replace("%N", inputFile_name)
                        outputFile = outputFile.replace("%B", inputFile_basename)
                        outputFile = outputFile.replace("%P", inputFile_path)
                        outputFile = outputFile.replace("%E", inputFile_ext)

                    if preview == -1 :
                        if os.path.isfile(outputFile) :
                            if app.overwrite.get_active() == 0 :
                                answer_b = askyesno(_("File existing"), _("The outputfile already exists \n" \
                                "overWrite ? " ))
                                if False == answer_b :
                                    return False


                        if self.render.createNewPdf(ar_pages, ar_layout, ar_cahiers, outputFile, preview) :
                            if self.arw["show"].get_active() == 1 :

                                if 'linux' in sys.platform :
                                    subprocess.call(["xdg-open", outputFile])
                                else:
                                    os.startfile(outputFile)

        return [ar_pages, ar_layout, ar_cahiers]




    def readConditions(self) :
        global optionsDict, config
        # Used by the go function above
        return
        if app.arw["entry3"].get() == "" :
            return
        else :
            inifile = app.arw["entry3"].get()
            config = parser.read(inifile)
            optionsDict = {}
            optionsDict["pages"] = {}
            optionsDict["conditions"] = {}

            if config.has_section("pages") :
                for a in config.options("pages") :
                    optionsDict["pages"][a] = config["pages"][a]

            if config.has_section("conditions") :
                for a in config.options("conditions") :
                    optionsDict["conditions"][a] = config["conditions"][a]


    def test1(self, widget) :
        print("input")
    def test2(self, widget) :
        print("output")
    def test3(self, widget) :
        print("value_changed")
    def test4(self, widget) :
        print("change value")
    def test5(self, widget) :
        print("test5")




class pdfRender():

    def transform(self, row, column, page_number, output_page_number, file_number) :
        global optionsDict, config, rows_i

        # init variables

        V_offset = row * ury_i
        H_offset = column * urx_i
        cos_l = 1
        sin_l = 0

        transform_s = " %s %s %s %s %s %s cm  \n" % (cos_l , sin_l, -sin_l, cos_l , H_offset , V_offset)
        transformations = []

        # Transformations defined in gui

        # Transformations for page in position (row, col)
        section_s = str(rows_i - row) + "," + str(column + 1)
        if section_s in config :
            transform_s += self.transform2(section_s)

        # Transformations for page #:#
        pageId = str(file_number) + ":" + str(page_number + 1)
        if pageId in config :
            transform_s += self.transform2(pageId)

            # Debug !!!
            """  Je ne comprends pas le sens de ce bloc, qui refait une deuxième fois la transformation,
                si bien que quand, par exemple, on demande une transformation de 45, on obtient 90 !
            ht = ini.readmmEntry(config[pageId]["htranslate"])
            vt = ini.readmmEntry(config[pageId]["vtranslate"])
            sc = ini.readPercentEntry(config[pageId]["scale"])
            ro = ini.readNumEntry(config[pageId]["rotate"])
            try :
                pdfrotate = ini.readNumEntry(config[pageId]["pdfrotate"])
            except :
                pdfrotate = 0
            xs = ini.readPercentEntry(config[pageId]["xscale"])
            ys = ini.readPercentEntry(config[pageId]["yscale"])



            matrix_s = self.calcMatrix2(ht, vt,
                                       cScale = sc,
                                       xscale = xs,
                                       yscale = ys,
                                       cRotate = ro,
                                       Rotate = pdfrotate,
                                       vflip = config[pageId]["vflip"],
                                       hflip = config[pageId]["hflip"])



##            if "shuffler_rotate" in config[pageId] :
##                # we need the page size
##                pdfDoc = app.shuffler.pdfqueue[file_number-1]
##                page = pdfDoc.document.get_page(page_number)
##                pix_w, pix_h = page.get_size()
##
##                angle = int(config[pageId]["shuffler_rotate"])
##                pdfrotate +=   angle
##                if angle == 270 :
##                    vtranslate = float(vtranslate) + float(pix_w)
##                elif angle == 90 :
##                    htranslate = float(htranslate) + float(pix_h)
##                elif angle == 180 :
##                    htranslate = float(htranslate) + float(pix_w)
##                    vtranslate = float(vtranslate) + float(pix_h)


            transform_s += self.calcMatrix2(ht, vt,
                                       cScale = sc,
                                       cRotate = ro,
                                       Rotate = pdfrotate)
            """
        # Transformations for even and odd pages
        if page_number % 2 == 1 :
            transform_s += self.transform2("even")
        if page_number % 2 == 0 :
            transform_s += self.transform2("odd")




        # Transformations defined in ini file

        if "pages" in config :
            pages_a = config["pages"].keys()
            if section_s in pages_a :               # If the layout page presently treated is referenced in [pages]
                temp1 = config["pages"][section_s]
                transformations += temp1.split(", ")

            if str(page_number + 1) in pages_a :    # If the page presently treated is referenced in [pages]
                temp1 = config["pages"][str(page_number + 1)]
                transformations += temp1.split(", ")


        if "conditions" in config :
            conditions_a = config["conditions"].keys()
            for line1 in conditions_a :
                condition_s = config["conditions"][line1]
                command_s, filters_s = condition_s.split("=>")
                if (eval(command_s)) :
                    transformations += filters_s.split(", ")

        for a in transformations :

            transform_s += self.calcMatrix(a)
        return (transform_s)

    def transform2(self, Id) :
        # Calculates matrix for a given section
        matrix_s = ""
        if Id in config :
            if not "pdfRotate" in config[Id] :
                config[Id]["pdfRotate"] = 0
            if not "rotate" in config[Id] :
                config[Id]["rotate"] = 0

            if "shuffler_rotate" in config[Id] :
                # we need the page size
                pdfDoc = app.shuffler.pdfqueue[file_number-1]
                page = pdfDoc.document.get_page(page_number)
                pix_w, pix_h = page.get_size()

                angle = int(config[Id]["shuffler_rotate"])
                pdfrotate += angle
                if angle == 270 :
                    vtranslate = float(vtranslate) + float(pix_w)
                elif angle == 90 :
                    htranslate = float(htranslate) + float(pix_h)
                elif angle == 180 :
                    htranslate = float(htranslate) + float(pix_w)
                    vtranslate = float(vtranslate) + float(pix_h)

            ht = ini.readmmEntry(config[Id]["htranslate"])
            vt = ini.readmmEntry(config[Id]["vtranslate"])
            sc = ini.readPercentEntry(config[Id]["scale"])
            ro = ini.readNumEntry(config[Id]["rotate"])
            xs = ini.readPercentEntry(config[Id]["xscale"])
            ys = ini.readPercentEntry(config[Id]["yscale"])



            matrix_s = self.calcMatrix2(ht, vt,
                                       cScale = sc,
                                       xscale = xs,
                                       yscale = ys,
                                       cRotate = ro,
                                       Rotate = ini.readNumEntry(config[Id]["pdfRotate"]),
                                       vflip = ini.readBoolean(config[Id]["vflip"]),
                                       hflip = ini.readBoolean(config[Id]["hflip"]))



        return matrix_s


    def calcMatrix(self, mydata, myrows_i = 1, mycolumns_i = 1) :
            # Calculate matrix for transformations defined in the configuration
            global config

            trans = mydata.strip()
            cos_l = 1
            cos2_l = 1
            sin_l = 0
            Htranslate = 0
            Vtranslate = 0


            if config.has_option(trans, "PdfRotate") :
                Rotate = config.getint(trans, "PdfRotate")
                sin_l = math.sin(math.radians(Rotate))
                cos_l = math.cos(math.radians(Rotate))
                cos2_l = cos_l

            if config.has_option(trans, "Rotate") :
                try :
                    Rotate = config.getfloat(trans, "Rotate")
                except :
                    pass

                sin_l, cos_l, HCorr, VCorr = self.centeredRotation(Rotate, myrows_i, mycolumns_i)
                cos2_l = cos_l

                Htranslate += HCorr
                Vtranslate += VCorr


            if config.has_option(trans, "Scale") :
                Scale_f = ini.readPercentEntry(config[trans]["Scale"])
                cos_l = cos_l * Scale_f
                cos2_l = cos_l

                HCorr = (urx_i * mycolumns_i * (Scale_f - 1)) / 2
                VCorr = (ury_i * myrows_i * (Scale_f - 1)) / 2
                Htranslate -= HCorr
                Vtranslate -= VCorr

            if config.has_option(trans, "xscale") :
                Scale_f = ini.readPercentEntry(config[trans]["xscale"])
                cos_l = cos_l * Scale_f

                HCorr = (urx_i * mycolumns_i * (Scale_f - 1)) / 2
                Htranslate -= HCorr

            if config.has_option(trans, "yScale") :
                Scale_f = ini.readPercentEntry(config[trans]["yScale"])
                cos2_l = cos2_l * Scale_f

                VCorr = (ury_i * myrows_i * (Scale_f - 1)) / 2
                Vtranslate -= VCorr

            # Vertical flip  : 1 0 0 -1 0 <height>
            if config.has_option(trans, "vflip") :
                value = config[trans]["vflip"].lower()
                if value == "true" or value == "1" :
                    cos2_l = cos2_l * (-1)
                    Vtranslate += ury_i * myrows_i

            # Horizontal flip  : -1 0 0 1 0 <width>
            if config.has_option(trans, "hflip") :
                value = config[trans]["hflip"].lower()
                if value == "true" or value == "1" :
                    cos_l = cos_l * (-1)
                    Htranslate += urx_i * mycolumns_i


            if config.has_option(trans, "htranslate") :
                ht = ini.readNumEntry(config[trans]["htranslate"])
                Htranslate += ht / adobe_l

            if config.has_option(trans, "vtranslate") :
                vt = ini.readNumEntry(config[trans]["vtranslate"])
                Vtranslate += vt / adobe_l



            if abs(sin_l) < 0.00001 : sin_l = 0          # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16
            if abs(cos_l) < 0.00001 : cos_l = 0



            transform_s = " %s %s %s %s %s %s cm  \n" % (cos_l , sin_l, -sin_l, cos2_l , Htranslate , Vtranslate)


            if "Matrix" in config[trans] :
                Matrix = config[trans]["Matrix"]
                transform_s = " %s  cm  \n" % (Matrix)


            return transform_s


    def calcMatrix2(self, Htranslate, Vtranslate,
                          cScale = 1, Scale = 1,
                          Rotate = 0, cRotate = 0,
                          vflip = 0, hflip = 0,
                          xscale = 1, yscale = 1,
                          global_b = False) :
            # calculate matrix for transformations defined in parameters

            Htranslate = float(Htranslate)
            Vtranslate = float(Vtranslate)
            cos_l = 1
            sin_l = 0

            if global_b == True :   # for global transformations, reference for centered scale, rotation and flip is the output page
                myrows_i = rows_i
                mycolumns_i = columns_i
            else :                  # for page transformations, reference is the active source page
                myrows_i = 1
                mycolumns_i = 1

            if Scale != 1:
                Scale_f = float(Scale)
            elif cScale != 1 :
                Scale_f = float(cScale)
            else :
                Scale_f = 1

            if Rotate != 0 :
                sin_l = math.sin(math.radians(float(Rotate)))
                cos_l = math.cos(math.radians(float(Rotate)))
            # TODO Rotate and cRotate are not compatible.
            elif cRotate != 0 :
                sin_l, cos_l, HCorr, VCorr = self.centeredRotation(float(cRotate), myrows_i, mycolumns_i)
                Htranslate += (HCorr * Scale_f)
                Vtranslate += (VCorr * Scale_f)

            if Scale != 1 :
                sin_l = sin_l * Scale_f
                cos_l = cos_l * Scale_f
                HCorr = (urx_i * (Scale_f - 1)) / 2
                VCorr = (ury_i * (Scale_f - 1)) / 2

            if cScale != 1 :
                sin_l = sin_l* Scale_f
                cos_l = cos_l * Scale_f
                HCorr = (urx_i * mycolumns_i * (Scale_f - 1)) / 2
                VCorr = (ury_i * myrows_i * (Scale_f - 1)) / 2

                Htranslate -= HCorr
                Vtranslate -= VCorr


            if abs(sin_l) < 0.00001 : sin_l = 0          # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16
            if abs(cos_l) < 0.00001 : cos_l = 0

            transform_s = " %s %s %s %s %s %s cm  \n" % (cos_l , sin_l, -sin_l, cos_l , Htranslate , Vtranslate)


            Htranslate = 0
            Vtranslate = 0
            cos_l = 1
            cos2_l = 1
            sin_l = 0


            if xscale != '1' and xscale != 1 :
                xscale = float(xscale)
                cos_l = cos_l * xscale

                HCorr = (urx_i * mycolumns_i * (xscale - 1)) / 2
                Htranslate -= HCorr

            if yscale != '1' and yscale != 1:
                yscale = float(yscale)
                cos2_l = cos2_l * yscale

                VCorr = (ury_i * myrows_i * (yscale - 1)) / 2
                Vtranslate -= VCorr

            if abs(sin_l) < 0.00001 : sin_l = 0          # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16
            if abs(cos_l) < 0.00001 : cos_l = 0

            transform_s += " %s %s %s %s %s %s cm  \n" % (cos_l , sin_l, -sin_l, cos2_l , Htranslate , Vtranslate)


            Htranslate = 0
            Vtranslate = 0
            cos_l = 1
            cos2_l = 1
            sin_l = 0

            # Vertical flip  : 1 0 0 -1 0 <height>
            if vflip != 0 and vflip != False  :
                cos2_l = cos2_l * (-1)
                Vtranslate += ury_i * myrows_i

            # Horizontal flip  : -1 0 0 1 0 <width>
            if hflip != 0 and hflip != False :
                cos_l = cos_l * (-1)
                Htranslate += urx_i * mycolumns_i



            if abs(sin_l) < 0.00001 : sin_l = 0          # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16
            if abs(cos_l) < 0.00001 : cos_l = 0
            if abs(cos2_l) < 0.00001 : cos2_l = 0

            transform_s += " %s %s %s %s %s %s cm  \n" % (cos_l , sin_l, -sin_l, cos2_l , Htranslate , Vtranslate)
            return transform_s

    def centeredRotation_old(self, Rotate) :

        Rotate = math.radians(Rotate)
        sin_l = math.sin(Rotate)
        cos_l = math.cos(Rotate)

        # If a is the angle of the diagonale, and R the rotation angle, the center of the rectangle moves like this :
        # Horizontal move = sin(a + R) - sin(a)
        # Vertical move   = cos(a + R) - cos(a)
        #Hence, corrections are sin(a) - sin(a+R) and cos(a) - cos(a-R)

        diag = math.pow((urx_i * urx_i) + (ury_i * ury_i), 0.5)
        alpha = math.atan2(ury_i, urx_i)

        S1 = math.sin(alpha)
        S2 = math.sin(alpha + Rotate)

        C1 = math.cos(alpha)
        C2 = math.cos(alpha + Rotate)

        Vcorr = (S1 - S2) * diag / 2
        Hcorr = (C1 - C2) * diag / 2

        return (sin_l, cos_l, Hcorr, Vcorr)

    def centeredRotation(self, Rotate, myrows_i = 1, mycolumns_i = 1) :

        Rotate = math.radians(Rotate)
        sin_l = math.sin(Rotate)
        cos_l = math.cos(Rotate)

        # If a is the angle of the diagonale, and R the rotation angle, the center of the rectangle moves like this :
        # Horizontal move = sin(a + R) - sin(a)
        # Vertical move   = cos(a + R) - cos(a)
        #Hence, corrections are sin(a) - sin(a+R) and cos(a) - cos(a-R)

        oWidth_i = urx_i * mycolumns_i
        oHeight_i = ury_i * myrows_i

        diag = math.pow((oWidth_i * oWidth_i) + (oHeight_i * oHeight_i), 0.5)
        alpha = math.atan2(oHeight_i, oWidth_i)

        S1 = math.sin(alpha)
        S2 = math.sin(alpha + Rotate)

        C1 = math.cos(alpha)
        C2 = math.cos(alpha + Rotate)

        Vcorr = (S1 - S2) * diag / 2
        Hcorr = (C1 - C2) * diag / 2

        return (sin_l, cos_l, Hcorr, Vcorr)

    def CalcAutoScale(self, fileNum, page) :
        global inputFiles_a, inputFile_a, refPageSize_a

        fileName = inputFiles_a[fileNum]
        page0 = inputFile_a[fileName].getPage(page)
        llx_i=page0.mediaBox.getLowerLeft_x()
        lly_i=page0.mediaBox.getLowerLeft_y()
        urx_i=page0.mediaBox.getUpperRight_x()
        ury_i=page0.mediaBox.getUpperRight_y()

        page_width = float(urx_i) - float(llx_i)
        page_height =  float(ury_i) - float(lly_i)

        (ref_width, ref_height) = refPageSize_a

        delta1 = ref_height / page_height
        delta2 = ref_width  / page_width
        Vdiff = ref_height - (page_height * delta2)
        Hdiff = ref_width - (page_width * delta1)

        # Choose the lower factor, and calculate the translation for centering the image
        if delta1 < delta2 :
            Scale = delta1
            Vtranslate = 0
            Htranslate = Hdiff/2
        else:
            Scale = delta2
            Vtranslate = Vdiff/2
            Htranslate = 0

        return (Scale, Htranslate, Vtranslate)


    def parsePageSelection(self, selection = "", append_prepend = 1) :
        global pagesSel, totalPages, pgcount, input1, numPages, step_i, blankPages
        global inputFiles_a, inputFile_a

        if len(inputFiles_a) == 0 :
            showwarning(_("No file loaded"), _("Please select a file first"))
            return False

        if selection == "" :
            selection = app.selection_s

        if selection.strip() == "" :        # if there is no selection, then we create the default selection
                                            # which contains all pages of all documents
            i = 1
            for f in inputFiles_a :
                fileName = inputFiles_a[f]
                numPages = inputFile_a[fileName].getNumPages()
                selection += str(i) + ":1-%s;" % (numPages)
                i += 1
            app.selection_s = selection

        if selection == "" :                # This should not happen...
            showwarning(_("No selection"), _("There is no selection"))
            return False

        selection = re.sub("[;,]{2,}", ";", selection)          # we replace successive ; or , by a single ;
        syntax_s = re.sub("[0-9,;:b\-\s]*", "", selection)      # To verify all characters are valid, we remove all them and there should remain nothing
        syntax_s = syntax_s.strip()
        if syntax_s != "" :                                     # If something remains, it is not valid
            showwarning(_("Invalid data"), _("Invalid data for Selection : %s. Aborting \n") % syntax_s)
            return False

        if append_prepend == 1 :
            pagesSel = prependPages * ["1:-1"]
        else :
            pagesSel = []


        selection = selection.replace(";", ",")
        selection = selection.strip()
        if selection[-1:] == "," :      # remove the trailing ,
            selection = selection[0:-1]
        list1 = selection.split(",")

        for a in list1 :
            a = a.strip()
            b = a.split(":")
            if (len(b) == 1) :
                docId_s = "1:"
            else :
                docId_s = b[0] + ":"
                a = b[1]

            if a.count("-") > 0:
                list2 = a.split("-")
                serie = list(range(int(list2[0]) - 1, int(list2[1])))
                for x in serie :
                    page_s = docId_s + str(x)
                    pagesSel = pagesSel + [page_s]
            elif a[-1:] == "b" :
                if a[0:-1].strip() == "" :
                    blank_pages_i = 1
                else :
                    blank_pages_i = int(a[0:-1])
                pagesSel = pagesSel + (["1:-1"] * blank_pages_i)
            else :
                try :
                    a = str(int(a) - 1)
                    pagesSel = pagesSel + [docId_s + a]
                except :
                    alert("Invalid selection ", a)
        if append_prepend == 1 :
            pagesSel = pagesSel + appendPages * ["1:-1"]

            if ini.booklet > 0 :
                step_i = 2
                blankPages = (len(pagesSel) % -4) * -1
                #app.print2(_("Blank pages to be added : %s") % (blankPages) , 1)

            else :
                if step_i < 1 :
                    step_i = 1
                blankPages = (len(pagesSel) % (step_i * -1)) * -1

            pagesSel += ["1:-1"] * blankPages



        totalPages = len(pagesSel)
        pgcount = totalPages

        return True


    def createPageLayout(self, logdata = 1) :
        global config, rows_i, columns_i, cells_i, step_i, sections, output, input1, adobe_l
        global numfolio,prependPages, appendPages, ref_page, selection
        global numPages, blankPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l, pgcount

        ar_pages = {}
        ar_cahiers = {}
        index=0
        last=0
        cells_i = columns_i * rows_i


        # Create booklets
        if ini.booklet > 0 :

            multiple_bkt = int(cells_i / 2)
            ini.radioDisp= 1


            folios =  pgcount / 4
            if numfolio == 0 :                  # single booklet
                numcahiers = 1
            else :                              # multiple booklets
                numcahiers = int(folios / numfolio)
                if (folios % numfolio > 0) :
                    numcahiers = numcahiers + 1

            # equilibrate booklets size
            minfolios = int(folios / numcahiers)
            restefolios = folios - (minfolios * numcahiers)

            for k in range(numcahiers) :
                if (k < restefolios) :              # number of folios in each booklet
                    ar_cahiers[k] = minfolios + 1
                else :
                    ar_cahiers[k] = minfolios

                first = last + 1                            # num of first page of the booklet
                last = first + (ar_cahiers[k] * 4) - 1      # num of the last page of the booklet

                if logdata == 1 :
                    #app.print2(_( "Booklet %s : pages %s - %s") % (k + 1, first, last), 1)
                    pass

                bkltPages = (last - first) + 1              # number of pages in the booklet
                for i in range (bkltPages // 2) :           # page nums for each sheet
                    if ((i % 2) == 0) :                     # Page paire à gauche
                        pg2 = (i + first)
                        pg1 = (last - i)
                    else :
                        pg1 = (i + first)
                        pg2 = (last - i)

                    ar_pages[index] = [pagesSel[pg1 - 1], pagesSel[pg2 - 1]] * multiple_bkt
                    index += 1

        else :
            while index < (pgcount / step_i) :
                start = last
                last = last + step_i
                pages = []
                for a in range(start, start + cells_i) :
                    PageX = start + (a % step_i)
                    if PageX > totalPages :
                        pages = pages + [-1]
                    else :
                        pages = pages + [pagesSel[PageX]]
                ar_pages[index] = pages
                index += 1


        # create layout
        ar_layout = []
        if ini.radioDisp == 1 :
            for r in range(rows_i) :
                r2 = rows_i - (r + 1)       # rows are counted from bottom because reference is lower left in pdf so we must invert
                for c in range (columns_i) :
                    ar_layout += [[r2, c]]
        elif ini.radioDisp == 2 :
            for c in range(columns_i) :
                for r in range(rows_i) :
                    r2 = rows_i - (r + 1)   # rows are counted from bottom so we must invert
                    ar_layout += [[r2, c]]

        # If option "Right to left" has been selected creates inverted layout (which will overwrite the previous one)

        if bool_test(config["options"]["righttoleft"]) == True :

            # create inverted layout
            ar_layout = []
            if ini.radioDisp == 1 :
                for r in range(rows_i) :
                    r2 = rows_i - (r + 1)         # rows are counted from bottom because reference is lower left in pdf so we must invert
                    for c in range (columns_i) :
                        c2 = columns_i - (c + 1)  # Invert for right to left option
                        ar_layout += [[r2, c2]]
            elif ini.radioDisp == 2 :
                for c in range(columns_i) :
                    c2 = columns_i - (c + 1)      # Invert for right to left optionInvert for right to left option Invert for right to left option
                    for r in range(rows_i) :
                        r2 = rows_i - (r + 1)     # rows are counted from bottom so we must invert
                        ar_layout += [[r2, c2]]

            # End of inverted layout


        # User defined layout
        if "radiopreset8" in app.arw and app.arw["radiopreset8"].get_active() == 1 :

            # number of sheets of this layout
            sheets = len(ini.imposition)

            # create blank pages if necessary
            rest = len(ar_pages) % sheets

            if rest > 0 :
                blank = []
                for i in range(len(ar_pages[0])) :
                    blank.append(["1:-1"])
                for i in range(rest) :
                    ar_pages[len(ar_pages) + i] = blank


            # create a copy of ar_pages

            ar_pages1 = {}
            for key in ar_pages :
                pages1 = ar_pages[key]
                temp = []
                for a in pages1 :
                    temp.append(a)
                ar_pages1[key] = temp


            if sheets == 1 :
                userpages = ini.imposition[0]

                for key in ar_pages :
                    pages = ar_pages[key]
                    pages1 = ar_pages1[key]

                    i = 0
                    # we reorder the pages following the order given in userpages
                    for value in userpages :
                        if value == "b" :
                            pages[i] = "1:-1"
                        else :
                            row = int(value) - 1
                            pages[i] = pages1[row]
                        i += 1
                    ar_pages[key] = pages

            elif sheets == 2 :              # more complicated...

                userpagesA = app.imposition[0]
                userpagesB = app.imposition[1]
                step = len(userpagesA)

                for key in range(0,len(ar_pages),2) :

                    pagesA = ar_pages[key]
                    pages1A = ar_pages1[key]
                    pagesB = ar_pages[key + 1]
                    pages1B = ar_pages1[key + 1]


                    i = 0
                    # we reorder the pages following the order given in userpages
                    for value in userpagesA :
                        if value == "b" :
                            pagesA[i] = "1:-1"
                        else :
                            row = int(value) - 1
                            if row < step :
                                index = pages1A[row]
                            else :
                                rowB = row % step
                                index = pages1B[rowB]
                            pagesA[i] = index
                        i += 1
                    i = 0
                    for value in userpagesB :
                        if value == "b" :
                            pagesB[i] = "1:-1"
                        else :
                            row = int(value) - 1
                            if row < step :
                                index = pages1A[row]
                            else :
                                rowB = row % step
                                index = pages1B[rowB]
                            pagesB[i] = index
                        i += 1
                    ar_pages[key] = pagesA
                    ar_pages[key + 1] = pagesB

        return (ar_pages, ar_layout, ar_cahiers)



    def createNewPdf(self, ar_pages, ar_layout, ar_cahiers, outputFile, preview = -1) :
        global debug_b, inputFile_a, inputFiles_a, result
        global mediabox_l
        global outputStream


        if debug_b == 1 :
            logfile_f = open ("log.txt", "wb")
        # status
        statusTotal_i = len(ar_pages)
        statusValue_i = 1


        time_s=time.time()
        output = PdfFileWriter()



        if preview >= 0 :           # if this is a preview
            outputStream = io.BytesIO()

        else :
            try :
                outputStream = open(outputFile, "wb")
            except :
                showwarning(_("File already open"), _("The output file is already opened \n" \
                "probably in Adobe Reader. \n" \
                "Close the file and start again"))
                return

        if preview >= 0 :           # if this is a preview
            output_page_number = preview + 1
        else :
            output_page_number = 1
##            # encryption
##            if app.permissions_i != -1 and app.password_s != "" :       # if permissions or password were present in the file
##                output.encrypt("", app.password_s, P = app.permissions_i)     # TODO : there may be two passwords (user and owner)
        for a in ar_pages :
            # create the output sheet
            page2 = output.addBlankPage(100,100)
            newSheet = page2.getObject()
            newSheet[generic.NameObject("/Contents")] = generic.ArrayObject([])
            newSheet.mediaBox.upperRight = mediabox_l       # output page size
            newSheet.cropBox.upperRight = mediabox_l
            # global rotation
            if ("options" in config) and ("globalRotation" in config["options"]) :
                gr = config["options"]["globalRotation"]
                gr_s = gr.strip()[14:]
                gr_i = int(gr_s)
                if gr_i > 0 :
                        newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(gr_i)

            i = 0
            ar_data = []


            if outputScale != 1 and app.autoscale.get_active() == 1 :
                temp1 = "%s 0 0 %s 0 0 cm \n" % (str(outputScale), str(outputScale))
                ar_data.append([temp1])


            #Output page transformations
            if "output" in config :
                OHShift = ini.readmmEntry(config["output"]["htranslate"])
                OVShift = ini.readmmEntry(config["output"]["vtranslate"])
                OScale = ini.readPercentEntry(config["output"]["scale"])
                ORotate = ini.readNumEntry(config["output"]["rotate"])

                Ovflip = ini.readBoolean(config["output"]["vflip"])
                Ohflip = ini.readBoolean(config["output"]["hflip"])

                Oxscale = ini.readPercentEntry(config["output"]["xscale"])
                if Oxscale == None : return False

                Oyscale = ini.readPercentEntry(config["output"]["yscale"])
                if Oyscale == None : return False



                temp1 = self.calcMatrix2(OHShift, OVShift,
                                         cScale = OScale,
                                         cRotate = ORotate,
                                         vflip = Ovflip,
                                         hflip = Ohflip,
                                         xscale = Oxscale,
                                         yscale = Oyscale,
                                         global_b = True)
                ar_data.append([temp1])



            # Transformations defined in ini file


            if "pages" in config :
                pages_a = config["pages"].keys()
                if "@" + str(output_page_number) in pages_a :               # If the output page presently treated is referenced in [pages]
                    temp1 = config["pages"]["@" + str(output_page_number)]
                    transformations = temp1.split(", ")
                    for name_s in transformations :
                        if config.has_option(name_s, "globalRotation") :
                            gr_s = config[name_s.strip()]["globalRotation"]
                            gr_i = int(gr_s)
                            if gr_i == 90 :
                                newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(90)
                            elif gr_i == 180 :
                                newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(180)
                            elif gr_i == 270 :
                                newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(270)
                        else :
                            transform_s = self.calcMatrix(name_s, rows_i, columns_i)
                            ar_data.append([transform_s])




            if "output_conditions" in config :
                conditions_a = config["output_conditions"].keys()
                for line1 in conditions_a :
                    condition_s = config["output_conditions"][line1]
                    command_s, filters_s = condition_s.split("=>")
                    if (eval(command_s)) :
                        transformations = filters_s.split(", ")
                        for name_s in transformations :
                            if "globalRotation" in config[name_s.strip()] :
                                gr_s = config[name_s.strip()]["globalRotation"]
                                gr_i = int(gr_s)
                                if gr_i == 90 :
                                    newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(90)
                                elif gr_i == 180 :
                                    newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(180)
                                elif gr_i == 270 :
                                    newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(270)
                            else :
                                transform_s = self.calcMatrix(name_s, rows_i, columns_i)
                                ar_data.append([transform_s])


            oString = ""

            # Ready to create the page
            for r, c in ar_layout :         # We are creating the page in row r and column c
                data_x = []
                if ar_pages[0] == [] :      # system not yet initialised
                    return
                file_number, page_number = ar_pages[a][i].split(":")
                file_number = int(file_number)
                page_number = int(page_number)
                if (page_number < 0) :
                    i += 1
                    continue          # blank page


                data_x.append("q\n")
                # Create the transformation matrix for the page
                matrix_s = self.transform(r, c, page_number, output_page_number, file_number)
                if matrix_s == False :
                    return False
                data_x.append(matrix_s)

                # Booklets : option "creep"
                if ini.booklet > 0 :                # This option makes sense only for booklets
                    creep_f1 = app.readmmEntry(app.arw["creep"])
                    if creep_f1 > 0 :
                        # calculate creep for each booklet (number of folios may vary)
                        for key, value in ar_cahiers.items() :
                            creep_f = creep_f1 / value

                        # reset to 0 for each booklet
                        # Starting page of each booklet
                        start_pages = [0]
                        mem = 0
                        for key, value in ar_cahiers.items() :
                            page_value = value * 2              # ar_cahiers gives the number of folios, not pages
                            start_pages.append(page_value + mem)
                            mem += page_value

                        # Calculate creep for a given page

                        for start_page in start_pages :
                            if output_page_number <= start_page :
                                page_in_booklet = output_page_number - start_page   # page number inside a given booklet
                                Htrans = creep_f * (int(page_in_booklet/2) - (page_in_booklet % 2))         # increment every two pages.
                                                        # It is a bit difficult to explain...
                                                        # We must get this :
                                                        #       page -5 => -3       External page
                                                        #       page -4 => -2
                                                        #       page -3 => -2
                                                        #       page -2 => -1
                                                        #       page -1 => -1
                                                        #       page 0  => 0        Internal page

                                if c == 0 :
                                    data_x.append(self.calcMatrix2(Htrans , 0))     # shift left
                                else :
                                    data_x.append(self.calcMatrix2((Htrans * -1), 0))  # shift right
                                break


                # scale the page to fit the output sheet, if required
                if"autoscale" in config["options"]:
                    if ini.readBoolean(config["options"]["autoscale"]) == True :
                        (scaleFactor_f, Htranslate, Vtranslate) = self.CalcAutoScale(file_number, page_number)
                        matrix1_s = self.calcMatrix2(Htranslate, Vtranslate, Scale = scaleFactor_f)
                        data_x.append(matrix1_s)

                file_name = inputFiles_a[file_number]
                newPage = inputFile_a[file_name].getPage(page_number)

                data_x.append(newPage)

                # Add page number if required
                # Choose font
                try:
                    temp1 = newPage['/Resources'].getObject()
                    if isinstance(temp1, dict) :
                        temp2 = temp1['/Font'].getObject()
                        temp3 = temp2.keys()
                        if '/F1' in temp3 :
                            font = '/F1'
                        else :          # we get the first font in the list
                            for k in temp3 :
                                font = k
                                break
                except :
                    pass            # Not critical. Default font will be used, but it does not show in the preview, that's why it is better to have an available font number.


                if app.arw["page_numbers"].get_active() == True :
                    font_size = ini.readIntEntry(app.arw["numbers_font_size"], default = 18)
                    bottom_margin = ini.readIntEntry(app.arw["numbers_bottom_margin"], default = 20)
                    start_from = ini.readIntEntry(app.arw["numbers_start_from"])
                    position = urx_i / 2
                    if start_from <= page_number + 1 :
                        data_x.append(" q BT %s %d Tf  1 0 0 1 %d %d Tm (%d) Tj ET Q\n" % (font, font_size, position, bottom_margin, page_number + 1))
                data_x.append("Q\n")
                if len(ini.delete_rectangle) > 0 :
                    (x1,y1,w1,h1) = ini.delete_rectangle
                    data_x.append("q n 1 1 1 rg \n")        # Couleur RGB ; 1 1 1 = white; 0,0,0 = black
                    data_x.append(" n %d %d %d %d re f* Q\n" % (x1,y1,w1,h1))  # rectangle : x, y, width, height
                ar_data.append(data_x)

                i += 1

            aa = urx_i
            bb = ury_i


            datay = []
            for datax in ar_data :
                datay += datax + ["\n"]

##            datay += ["q n 10 10 m 10 122 l S \n"]
##            datay += ["  n 10 10 m 72 10  l S \n"]
##            datay += ["Q\n"]
            if preview > 0 :
                newSheet.mergePage3(datay)                  # never use slow mode for preview
            elif ("slowMode" in app.arw
                   and app.arw["slowMode"].get_active() == 0) :        # normal mode
                        newSheet.mergePage3(datay)

            else :                                          # slow mode (uses mergePage instead of mergePage3)

                dataz = ""
                pages = []
                end_code = ""
                for data2 in datay :
                    if not isinstance(data2, str) :

                        pages.append([data2, dataz])
                        dataz = ""
                    else :
                        dataz += data2
                if not (dataz == "" or len(pages) == 0) :    # skip blank pages

                    i = len(pages)
                    pages[i - 1].append(dataz)

                for content in pages :
                    if len(content) == 3 :
                        end_code == content[2]
                    else :
                        end_code = ""
                    newSheet.mergeModifiedPage(content[0], content[1], end_code)




            if ( "noCompress" in app.arw
                  and app.arw["noCompress"].get_active() == 0) :
                        newSheet.compressContentStreams()


            if preview == -1 :      # if we are creating a real file (not a preview)
                message_s = _("Assembling pages: %s ")   % (ar_pages[a])
                app.print2( message_s , 1)
                app.status.set_text("page " + str(statusValue_i) + " / " + str(statusTotal_i))
                statusValue_i += 1
            output_page_number += 1
            while Gtk.events_pending():
                            Gtk.main_iteration()

        time_e=time.time()

        #app.print2(_("Total length : %s ") % (time_e - time_s), 1)

        output.write(outputStream)
        if preview == -1 :          # if we are creating a real file (not a preview)
            outputStream.close()    # We must close the file, otherwise it is not possible to see it in the Reader
                                    # TODO : after closing the reader, preview should be automatically updated.

        del output

        if debug_b == 1 :
            logfile_f.close()

        # TODO
        """
        if preview == -1 :      # if we are creating a real file (not a preview)
            if app.settings.get_active() == 1 :
                app.saveProjectAs("",inputFile + ".ini")
        """
        return True




def printTree(curPage,out) :

        #curPage = source.getPage(page)
        keys_a = list(curPage.keys())
        #temp1 = curPage["/Parent"].getObject()
        for j in keys_a :
            #if j in ["/Parent", "/Rotate", "/MediaBox", "/Type", "/Annots", "/Contents"] :
            if j in ["/Parent"] :
                continue
            temp1 = curPage[j].getObject()
            print("======> page "  + str(page) + "  " + j, file=out)
            print(temp1, file=out)
            if isinstance(temp1, dict) :
                for k in temp1 :
                    temp2 = temp1[k].getObject()
                    print(str(k) + " : ", end=' ', file=out)
                    print(temp2, file=out)
                    if isinstance(temp2, dict) :
                        for l in temp2 :
                            temp3 = temp2[l].getObject()
                            print(str(l) + " : ", end=' ', file=out)
                            print(temp3, file=out)
                            if isinstance(temp3, dict) :
                                for m in temp3 :
                                    temp4 = temp3[m].getObject()
                                    print(str(m) + " : ", end=' ', file=out)
                                    print(temp4, file=out)
                                    if isinstance(temp4, dict) :
                                        for n in temp4 :
                                            temp5 = temp4[n].getObject()
                                            print(str(n) + " : ", end=' ', file=out)
                                            print(temp5, file=out)

    #out.close()


def parseOptions() :
    global arg_a
    parser = OptionParser()
    parser.add_option("-f", "--file", dest="filename",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-r", "--rows", dest="rows_i",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-c", "--columns", dest="columns_i",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-n", "--numfolio", dest="numfolio",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-b", "--booklet", dest="booklet",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-a", "--appendPages", dest="appendPages",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-p", "--prependPages", dest="prependPages",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-s", "--selection", dest="selection",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-o", "--output", dest="outputFile",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-i", "--iniFile", dest="iniFile",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-e", "--referencePage", dest="referencePage",
                      help="write report to FILE", metavar="FILE")

    parser.add_option("-q", "--quiet",
                      action="store_false", dest="verbose", default=True,
                      help="don't print status messages to stdout")

    (option_v, arg_a) = parser.parse_args()


##    if None != option_v.iniFile :
##            ini_s = option_v.iniFile
##            parseIniFile(ini_s)


def extractBase() :
    """
    extract absolute path to script
    @return prog_s : absolute program path
    @return pwd_s : current working dir
    @return base_s : dirname of prog_s
    """

    # read current dir
    prog_s = sys.argv[0]
    pwd_s = os.path.abspath(".")
    name_s = os.path.basename(prog_s)
    _sep_s = '\\'

    # extract program path
    # if path starts with \ or x:\ absolute path
    if _sep_s == prog_s[0] or \
       (2 < len(prog_s) and \
        ":" == prog_s[1] and
        _sep_s == prog_s[2]) :
        base_s = os.path.dirname(prog_s)
    # if it starts with ./  , relative path
    elif 1 < len(prog_s) and \
         "." == prog_s[0] and \
         _sep_s == prog_s[1] :
        path_s = os.path.abspath(prog_s)
        base_s = os.path.dirname(path_s)
    # if it is in the active directory
    elif os.path.exists(os.path.join(pwd_s, prog_s)) or \
        os.path.exists(os.path.join(pwd_s, prog_s) + ".exe"):       # Necessary if the user starts the program without the extension (maggy, without .exe)
        path_s = os.path.join(pwd_s, prog_s)
        base_s = os.path.dirname(path_s)
    else :
        tab_a = os.environ["PATH"].split(":")
        limit = len(tab_a)
        found = False
        for scan in range(limit) :
            path_s = os.path.join(tab_a[scan], prog_s)
            if os.path.exists(path_s) :
                base_s = os.path.dirname(path_s)
                found = True
                break
        if not found :
            raise ScriptRt("path to program is undefined")

    # application base import
    return(name_s, pwd_s, base_s)


def sfp(path) :
    # sfp = set full path
    return os.path.join(prog_path_u, path)

def sfp2(file1) :
    # sfp2 = set full path, used for temporary directory
    try:
        return os.path.join(share_path_u, file1)
    except :
        time.sleep(1)                 # Sometimes there was a sort of conflict with another thread
        return os.path.join(share_path_u, file1)

def sfp3(file1) :
    # sfp3 = set full path, used for config directory
    try:
        return os.path.join(cfg_path_u, file1)
    except :
        time.sleep(1)                 # Sometimes there was a sort of conflict with another thread

def close_applicationx(self, widget, event=None, mydata=None):

    if Gtk.main_level():
        app.arw["window1"].destroy()
        Gtk.main_quit()
    else:
        sys.exit(0)

    return False

###########################################################################
# MAIN ####################################################################
###########################################################################

def main() :

    global PdfShuffler, PDF_Doc

    from pdfbooklet.pdfshuffler_g3 import PdfShuffler, PDF_Doc

    global isExcept
    global startup_b
    global preview_b
    global project_b
    global openedProject_u
    global areaAllocationW_i
    global areaAllocationH_i

    global base_a
    global prog_path_u
    global temp_path_u
    global cfg_path_u
    global share_path_u

    global rows_i
    global columns_i
    global step_i
    global outputScale
    global outputStream_mem
    global mem

    isExcept = False
    startup_b = True
    preview_b = True
    project_b = False
    openedProject_u = ""
    areaAllocationW_i = 1
    areaAllocationH_i = 1
    rows_i = 1
    columns_i = 2
    step_i = 1
    outputScale = 1
    outputStream_mem = ""
    mem = {}
    mem["update"] = time.time()

    base_a = extractBase()
    prog_path_u = unicode2(base_a[2])


    errorLog = sys.argv[0] + ".log"
    argv_a = sys.argv
    sys.argv = [sys.argv[0]]            # remove any parameter because they are not supported by PdfShuffler
    if os.path.exists(sfp(errorLog)) :
        try :           # Sometimes the file may be locked
            os.remove(sfp(errorLog))
        except :
            pass

    # set directories for Linux and Windows

    if 'linux' in sys.platform :
        cfg_path_u = os.path.join(os.environ['HOME'], ".config", "pdfbooklet")
        if os.path.isdir(cfg_path_u) == False :
            os.mkdir(cfg_path_u)

        share_path_u = "/usr/share/pdfbooklet"
        if os.path.isdir(share_path_u) == False :
            os.mkdir(share_path_u)

    else:
        #TODO : this is not the recommended situation.
        cfg_path_u = prog_path_u
        share_path_u = prog_path_u



    #parseOptions()

    try:


        global render, app, parser, ini
        global inputFiles_a

        render = pdfRender()
        parser = myConfigParser()
        ini = TxtOnly(render)


        # command line processing
        if len(argv_a) > 2 and argv_a[2].strip() != "" :
            arg1 = argv_a[1]
            (name,ext) = os.path.splitext(arg1)     # determine file type from the extension
                                                    # TODO : determine from mimetype for Linux
            if ext == ".ini" :
               startup_b = False
               app = dummy()
               ini.openProject2(arg1)
               ini.output_page_size(1)
               app.pagesTr = copy.deepcopy(config)
               if render.parsePageSelection("1-20", 0) :

##                    self.readConditions()
                    ar_pages, ar_layout, ar_cahiers = render.createPageLayout()
                    if ar_pages != None :
                        render.createNewPdf(ar_pages, ar_layout, ar_cahiers, "test.pdf", -1)

            return True

        settings = Gtk.Settings.get_default()
        settings.props.gtk_button_images = True
        app = gtkGui(render)

        app.guiPresetsShow("booklet")
        app.resetTransformations(0)
        app.resetTransformations2(0)
        app.guiPresets(0)



        if os.path.isfile(sfp3("pdfbooklet.cfg")) == False :
            f1 = open(sfp3("pdfbooklet.cfg"), "w")
            f1.close()
        ini.parseIniFile(sfp3("pdfbooklet.cfg"))


        if len(argv_a) > 1 :            # a filename has been added in the command line
            if len(argv_a[1]) > 0 :     # this is not an empty string
                arg1 = argv_a[1]
                (name,ext) = os.path.splitext(arg1)     # determine file type from the extension
                                                        # TODO : determine from mimetype for Linux
                if ext == ".ini" :                      # If it is a project
                    startup_b = False
                    if not ini.openProject2(arg1) :     # if file not found
                        print ("Unknown file on the command line : " + arg1)
                    else :
                        app.arw["previewEntry"].set_text("1")
                    while Gtk.events_pending():
                                Gtk.main_iteration()
                    app.previewUpdate()

                    if len(argv_a) > 2 :
                        if argv_a[2].strip() == "-r" :
                            app.go("")          # TODO : should be go("", 0) but this creates errors
                            app.close_application("")

                elif ext == ".pdf" :
                    inputFiles_a = {}
                    inputFiles_a[1] = argv_a[1]
                    ini.loadPdfFiles()
                    app.selection_s = ""
                    app.arw["previewEntry"].set_text("1")
                    app.previewUpdate()
                else :
                    print("Unknown file type in the command line")


        startup_b = False

##        Gdk.threads_init()
##        Gdk.threads_enter()
        Gtk.main()
##        Gdk.threads_leave()

##        os._exit(0) # required because pdf-shuffler does not close correctly



    except :
        isExcept = True
        excMsg_s = "unexpected exception"
        (excType, excValue, excTb) = sys.exc_info()
        tb_a = traceback.format_exception(excType, excValue, excTb)
        for a in tb_a :
            print(a)

    # handle eventual exception
    if isExcept :

##        if app.shuffler != None :
##            app.shuffler.window.destroy()
        if Gtk.main_level():
##            Gtk.gdk.threads_enter()
            Gtk.main_quit()
##            Gtk.gdk.threads_leave()
            #os._exit(0)
            sys.exit(1)
        else:
            sys.exit(1)




if __name__ == '__main__' :
    main()
