1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# ****************************************************************************
5#  This program is free software; you can redistribute it and/or modify
6#  it under the terms of the GNU General Public License as published by
7#  the Free Software Foundation; either version 2 of the License, or
8#  (at your option) any later version.
9#
10#  This program is distributed in the hope that it will be useful,
11#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#  GNU General Public License for more details.
14#
15#  You should have received a copy of the GNU General Public License
16#  along with this program; if not, write to the Free Software
17#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18#
19# ****************************************************************************
20
21"""
22******************************************************************************
23
24DESCRIPTION & USAGE:
25This script needs Tkinter. It will create a GUI with an alphabetical list
26of fonts using the names as they will be shown in Scribus. User can select
27one or more fonts and create an example sheet(s) to print or create a PDF
28from. It is heavily commented to make it easier for the user to adjust it
29for his / her own needs.
30
31Note: this version needs read/write access to .scribus directory in users
32home. You will also need Python Imaging Library (PIL) installed.
33If your system does not meet these requirements then change showPreviewPanel
34to a value of 0. This will disable the new preview features.
35
36******************************************************************************
37
38First release    : 30/12/2003
39This release     : v0.8.1tk (released 4th Dec 2005)
40Copyright        : (C) 2003 - 2005 Steve Callcott
41Latest releases
42and support      : www.firstwish.co.uk/sjc/scribus/index.php
43Maintainer       : Steve Callcott 2003 - 2005
44Email            : stotte@firstwish.co.uk
45
46For revision history see the ChangeLog file.
47Bugs and future plans are listed in the TODO file.
48See NEWS for new features since last version.
49
50WHATS NEW v0.8.2tk:
51A one liner change by Jean Ghali to line #734 to add the extra parameter missing.
52See: http://bugs.scribus.net/view.php?id=4377
53
54WHATS NEW v0.8.1tk:
55After reloading users saved preferences the status bar was not showing
56correct calculations.
57Changed text in settings menu.
58
59WHATS NEW v0.8tk Final:
60Cleaned up the checkbox labels and layout.
61
62WHATS NEW v0.8tk Preview 3:
63Calls the new Scribus zoomDocument() function to make the completed font
64sample document fit in Scribus window.
65
66Grey out "Start page number count from first page" when "Print TOC" is
67not checked as without a table of contents the first page would always
68start on the same page number making this option irrelevant.
69
70WHATS NEW v0.8tk Preview 2:
71Replaced the newDoc() with newDocument(). Have not put any fallback code
72for use with earlier Scribus versions.
73
74When using double sided option we now make use of Scribus ability to display
75pages side by side as default. You may need to zoom out to view the
76complete document width.
77
78WHATS NEW v0.8tk Preview 1:
79Rearanged the initialisation. If no fonts are found for the Table of
80Contents, page numbers and font sample labels, the script shows a
81message box listing the problem and a possible solution as well as a message
82to the console.
83
84A Scribus messagebox alerts the user if Tkinter is not found. Previously
85this message was only printed to the console.
86
87Now able to do a dummy run to calculate and report the amount of samples
88that will fit on a page. This enables the script to correctly calculate
89how many sheets will be required. Previously it was always assumed that
90there would be 3 sample blocks on a sheet. This is now not always the case.
91
92Added menu. Also added "about" and "settings" dialogs.
93
94Sample rows can be selected or unselected to save on paper. The settings are
95automatically saved when changed and can be set as user defaults.
96
97User can choose to have page numbers count from first page of the toc instead
98of the first page of samples. This can be helpful if wanting to quickly look
99up a font in the toc and then using the Scribus page navigator dialog to go to
100the actual page on the screen to view it without printing it out.
101
102Added initial support for a sample paragraph. The sample paragraph defaults
103to "off" due to the amount of space it uses on the page.
104
105Some widgets read their defaults from a config dictionary.
106
107Many code cleanups. Classes used for settings storage have been replaced with
108dictionaries to make it easier for users to customise.
109
110******************************************************************************
111"""
112
113import sys
114import os
115import pickle
116
117
118showPreviewPanel = 1 # change to 0 to permanently hide the preview
119TEMP_PATH = os.path.join(os.path.expanduser('~'), '.scribus')
120CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.scribus/fontsampler')
121
122
123try:
124    import scribus
125except ImportError as err:
126    print ('This Python script is written for the Scribus scripting interface.')
127    print ('It can only be run from within Scribus.')
128    sys.exit(1)
129
130
131try:
132    from tkinter import *
133except ImportError as err:
134    print ('This script will not work without tkinter')
135    scribus.messageBox('Error','This script will not work without tkinter\nPlease install and try again',
136                    scribus.ICON_WARNING)
137    sys.exit(1)
138
139
140if not os.path.exists(CONFIG_PATH):
141    try:
142        print ('Attempting to creating configuration file directory...')
143        os.mkdir(CONFIG_PATH)
144        print ('Success, now testing for write access of new directory...')
145        if os.access(CONFIG_PATH, os.W_OK):
146            print ('Write access ok.')
147        else:
148            print ('Error, unable to write to .scribus/fontsampler directory.')
149    except:
150        CONFIG_PATH = ''
151        print ('Failed to make configuration file directory,')
152        print ('do you have a .scribus directory in your home directory?')
153        print ('font sampler will not be able to save your preferences')
154
155
156try:
157    from PIL import Image
158except ImportError as err:
159    print ('You need to install Python Imaging Library (PIL).')
160    print ('If using gentoo then you need to emerge /dev-python/imaging')
161    print ('If using an RPM based linux distribution then you add python-imaging or similar.')
162    print ('Script will continue without the font preview panel.')
163    showPreviewPanel = 0
164
165
166try:
167    from PIL import ImageTk
168except ImportError as err:
169    print ('Module ImageTk not found, font preview disabled')
170    showPreviewPanel = 0
171
172
173if showPreviewPanel:
174    if not os.path.exists(TEMP_PATH):
175        print ('.scribus folder not found, disabling font preview panel')
176        showPreviewPanel = 0
177    if not os.access(TEMP_PATH, os.W_OK):
178        print ('Unable to write to .scribus folder, disabling font preview panel')
179        showPreviewPanel = 0
180
181
182# A few globals for use later...
183gSamplePic = None
184gPreviewId = None
185
186#*************************************************************************
187
188WINDOW_TITLE = 'Font Sampler v0.8.1tk - Steve Callcott'
189SUPPORT_PAGE = 'www.firstwish.co.uk/sjc/scribus/index.php'
190
191fontsListFixed = (
192    'Luxi Mono Regular',
193    'Nimbus Mono L Regular',
194    'Courier 10 Pitch Regular',
195    'Courier New Regular',
196    'Courier Regular',
197    'Andale Mono Regular',
198    'Larabiefont Regular'
199)
200
201fontsListProportional = (
202    'Nimbus Sans L Regular',
203    'Luxi Sans Regular',
204    'Bitstream Vera Sans',
205    'Helvetica',
206    'Arial Regular'
207)
208
209defaultPrefs = {
210    'wantDoubleSided': 0,
211    'paperSize':'A4',           # currently PAPER_LETTER or PAPER_A4
212    'wantTOC': 1,
213    'wantBindingOffset': 0,
214    'wantPageNumbers': 1,
215    'wantPageOneOnFirst': 0,
216    'wantAlphabet' : 1,
217    'want6Point' : 1,
218    'want8Point' : 1,
219    'want10Point' : 1,
220    'want12Point' : 1,
221    'want16Point' : 1,
222    'want20Point' : 1,
223    'want32Point' : 1,
224    'wantParagraph' : 0         # Uses a lot of space so default is off
225}
226
227userPrefs = {}
228
229geometriesList = [
230    {
231        'paperName' : 'A4',
232        'paperSize' : scribus.PAPER_A4,
233        'paperWidth' : 595,
234        'paperHeight' : 842,
235        'paperTopMargin' : 60,
236        'paperBottomMargin' : 50,
237        'paperLeftMargin' : 50,
238        'paperRightMargin' : 50,
239        'paperBinding' : 16,
240        'tocRowsPerPage' : 57,
241        'paperPageNumVertOffset' : 16
242    },
243    {
244        'paperName' : 'US Letter',
245        'paperSize' : scribus.PAPER_LETTER,
246        'paperWidth' : 612,
247        'paperHeight' : 792,
248        'paperTopMargin' : 27,
249        'paperBottomMargin' : 45,
250        'paperLeftMargin' : 50,
251        'paperRightMargin' : 50,
252        'paperBinding' : 18,
253        'tocRowsPerPage' : 56,
254        'paperPageNumVertOffset' : 16
255    }
256]
257
258# define our data dictionary and some of the data...
259dD = {
260    'tocHeaderTitle' : 'Table of Contents',
261    'tocCharsInRow' : 75,
262    'previewpanelFontHeight' : 28,
263    'previewpanelSampleText' : 'Woven silk pyjamas exchanged for blue quartz'
264}
265
266samplesHeader = {
267    'fontSize' : 16,
268    'lineSpace' : 15,
269    'textHeight' : 23
270}
271
272# Use \xBC etc to insert Hex ascii chars into the sample strings below.
273sampleAlphabet = {
274    'fontSize' : 10.5,
275    'lineSpace' : 12,
276    'textString' : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#?$*&',
277    'textHeight' : 18
278}
279
280sample6Point = {
281    'fontSize' : 6,
282    'lineSpace' : 6,
283    'textString' : 'This line is in 6 point',
284    'textHeight' : 13
285}
286
287sample8Point = {
288    'fontSize' : 8,
289    'lineSpace' : 8,
290    'textString' : 'This line is in 8 point',
291    'textHeight' : 16
292}
293
294sample10Point = {
295    'fontSize' : 10,
296    'lineSpace' : 11,
297    'textString' : 'This line is in 10 point',
298    'textHeight' : 19
299}
300
301sample12Point = {
302    'fontSize' : 12,
303    'lineSpace' : 11,
304    'textString' : 'This line is in 12 point',
305    'textHeight' : 21
306}
307
308sample16Point = {
309    'fontSize' : 16,
310    'lineSpace' : 13,
311    'textString' : 'This line is in 16 point',
312    'textHeight' : 26
313}
314
315sample20Point = {
316    'fontSize' : 20,
317    'lineSpace' : 16,
318    'textString' : 'This line is in 20 point',
319    'textHeight' : 31
320}
321
322sample32Point = {
323    'fontSize' : 32,
324    'lineSpace' : 29,
325    'textString' : 'This line is in 32 point',
326    'textHeight' : 49
327}
328
329sampleParagraph = {
330    'fontSize' : 9,
331    'lineSpace' : 10.8,
332    'textHeight' : 175
333}
334
335sampleParagraphText = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut a sapien. \
336Aliquam aliquet purus molestie dolor. Integer quis eros ut erat posuere dictum. \
337Curabitur dignissim. Integer orci. Fusce vulputate lacus at ipsum. Quisque in libero \
338nec mi laoreet volutpat. Aliquam eros pede, scelerisque quis, tristique cursus, \
339placerat convallis, velit. Nam condimentum. Nulla ut mauris. Curabitur adipiscing, \
340mauris non dictum aliquam, arcu risus dapibus diam, nec sollicitudin quam erat quis \
341ligula. Aenean massa nulla, volutpat eu, accumsan et, fringilla eget, odio. \
342Nulla placerat porta justo. Nulla vitae turpis.\n\nPraesent lacus.Lorem ipsum dolor sit \
343amet, consectetuer adipiscing elit. Pellentesque habitant morbi tristique senectus \
344et netus et malesuada fames ac turpis egestas. Quisque vel erat eget diam \
345consectetuer iaculis. Cras ante velit, suscipit et, porta tempus, dignissim quis, \
346magna. Vivamus viverra, turpis nec rhoncus ultricies, diam turpis eleifend nisl, a \
347eleifend ante felis ac sapien. Integer bibendum. Suspendisse in mi non neque \
348bibendum convallis. Suspendisse potenti. Sed sit amet purus at felis adipiscing \
349aliquam. Vivamus et nisl sit amet mauris aliquet molestie. Integer tortor massa, \
350aliquam a, lacinia nonummy, sagittis nec, eros.'
351
352#*************************************************************************
353
354def set_font_fixed(fontList):
355    """Find a matching font for the Table of Contents."""
356    availableFonts = scribus.getFontNames()
357    found = 0
358    for f in fontList:
359        if found:
360            break
361        for i in availableFonts:
362            if not found:
363                if f == i:
364                    return f
365                    found = 1
366                    break
367    if not found:
368        errorList = ''
369        for j in fontList:
370            errorList = errorList + j + '\n'
371        errorMessage ='No suitable fixed width font found.\nPlease install at least one of these fixed width fonts:\n'+errorList
372        print (errorMessage)
373        raise Exception(errorMessage)
374
375
376def set_font_proportional(fontList):
377    """Find a matching font for the page numbers and font names above samples."""
378    availableFonts = scribus.getFontNames()
379    found = 0
380    for p in fontList:
381        if found:
382            break
383        for i in availableFonts:
384            if not found:
385                if p == i:
386                    return p
387                    found = 1
388                    break
389    if not found:
390        errorList = ''
391        for j in fontList:
392            errorList = errorList + j + '\n'
393        errorMessage = 'No suitable proportional font found.\nPlease install at least one of these proportional fonts:\n'+errorList
394        print (errorMessage)
395        raise Exception(errorMessage)
396
397
398def save_user_conf(path):
399    """Save the data to the save file on the path specified by CONFIG_PATH.
400
401    Note initialisation unsets the CONFIG_PATH if it failed to verify or create"""
402    if not path == '':
403        try:
404            file = open(os.path.join(path,'fontsampler.conf'), 'w')
405            data = {
406                'a' : defaultPrefs,
407                'b' : userPrefs
408            }
409            pickle.dump(data, file)
410            file.close()
411        except:
412            print ('failed to save data')
413
414
415def restore_user_conf(path):
416    """Restore the data from the save file on the path specified by CONFIG_PATH."""
417    try:
418        file = open(os.path.join(path,'fontsampler.conf'), 'r')
419        data = pickle.load(file)
420        file.close()
421        defaultPrefs.update(data['a'])
422        userPrefs.update(data['b'])
423    except:
424        userPrefs.update(defaultPrefs)
425        print ('failed to load saved data so using default values defined in the script')
426
427
428def set_page_geometry(dD, geometriesList, paperSize, wantBindingOffset):
429    """This is the experimental replacement paper size setting function.
430
431    Each paper size and other associated data are stored in a dictionary.
432    The dictionaries are stored in a list. We copy appropriate dictionary
433    and custom calculations into a work dictionary for use.
434    The advantage of this is its easier to add new paper definitions.
435    Returns a new dictionary, use .update to merge in new values into dD.
436    """
437    try:
438        result={}
439        for i in geometriesList:
440            if i['paperName'] == paperSize:
441                dD.update(i)
442
443        result['paperLeftMarginOdd'] = dD['paperLeftMargin'] + \
444                                       dD['paperBinding']
445        result['paperRightMarginEven'] = dD['paperRightMargin'] + \
446                                         dD['paperBinding']
447        result['paperTextHeight'] = dD['paperHeight'] - \
448                                    dD['paperTopMargin'] - \
449                                    dD['paperBottomMargin']
450        result['paperMargins'] =  dD['paperLeftMargin'],dD['paperRightMargin'],dD['paperTopMargin'],dD['paperBottomMargin']
451
452        # if we are adding a binding offset to the margins then we will have less width for our text...
453        if wantBindingOffset:
454            result['paperTextWidth'] = dD['paperWidth'] - \
455                                       dD['paperLeftMargin'] - \
456                                       dD['paperRightMargin'] - \
457                                       dD['paperBinding'] - \
458                                       2
459        else:
460            result['paperTextWidth'] = dD['paperWidth'] - \
461                                       dD['paperLeftMargin'] - \
462                                       dD['paperRightMargin'] - \
463                                       2
464        return result
465    except:
466        errorMessage = 'set_page_geometry() failure: %s' % sys.exc_info()[1]
467        print (errorMessage)
468
469
470def set_odd_even(pageNum):
471    """ Sets the left margin position.
472
473    Checks the number passed to it and sets left margin accordingly.
474    Call once after each new page is created.
475    Returns 1 if even and 0 if odd page.
476    """
477    if pageNum % 2 == 0:
478        isEvenPage = 1                                          # Even side
479    else:
480        isEvenPage = 0                                          # Odd side
481
482    if userPrefs['wantBindingOffset']:
483        if isEvenPage and userPrefs['wantDoubleSided']:         # Even (when double sided)
484            dD['paperLeftSide'] = dD['paperLeftMargin'] + 1
485        else:                                                   # Odd side
486            dD['paperLeftSide'] = dD['paperLeftMarginOdd'] + 1
487    else:                                                       # No binding
488        dD['paperLeftSide'] = dD['paperLeftMargin'] + 1
489    return isEvenPage
490
491
492def draw_sample_row(font, fontSize, lineSpace, textString, x, y, w, h, getSizeOnly):
493    """Creates one row of samples or a header for the top of the block.
494
495    Called once by draw_sample_block() to create a block label then as many times
496    as required to create each actual sample found in the list of dictionaries
497    containing each samples definition.
498    """
499    if not getSizeOnly:
500        f = scribus.createText(x, y, w, h)
501        scribus.insertText(textString, 0, f)
502        scribus.setFont(font, f)
503        scribus.setFontSize(fontSize, f)
504        scribus.setLineSpacing(lineSpace, f)
505        scribus.setTextAlignment(0, f)
506    return y + h + 1
507
508
509def draw_sample_block(fontName, x, y, w, getSizeOnly):
510    """Drawing of a complete sample block starts from here.
511
512    Iterates through each sample declared in the "samples" tuple. Places one
513    complete block using the font specified in fontname.
514    Note top line on page is drawn outside of this function. This ensures ease
515    of returning same height of every sample block. Line could have been drawn
516    at top inside this function and page finalised with line at bottom instead.
517    If getSizeOnly is true then returns the overall height of the entire text
518    block but without actually placing it.
519    """
520    startPos = y
521    # Note there are 2 points of space before horizontal line at bottom of block.
522    # This 2 points will not appear at top of page so need to add it.
523
524    # set space below horizontal line to the top of the text block
525    y = y + 4
526
527    # (note there is one extra point inserted by addSampleRow() for each row generated)...
528
529    # first need a header...
530    y = draw_sample_row(dD['bookstylePropFont'], samplesHeader['fontSize'], samplesHeader['lineSpace'], fontName, x, y, w, samplesHeader['textHeight'], getSizeOnly)
531
532    if userPrefs['wantAlphabet']:
533        y = draw_sample_row(fontName, sampleAlphabet['fontSize'], sampleAlphabet['lineSpace'], sampleAlphabet['textString'], x, y, w, sampleAlphabet['textHeight'], getSizeOnly)
534
535    if userPrefs['want6Point']:
536        y = draw_sample_row(fontName, sample6Point['fontSize'], sample6Point['lineSpace'], sample6Point['textString'], x, y, w, sample6Point['textHeight'], getSizeOnly)
537
538    if userPrefs['want8Point']:
539        y = draw_sample_row(fontName, sample8Point['fontSize'], sample8Point['lineSpace'], sample8Point['textString'], x, y, w, sample8Point['textHeight'], getSizeOnly)
540
541    if userPrefs['want10Point']:
542        y = draw_sample_row(fontName, sample10Point['fontSize'], sample10Point['lineSpace'], sample10Point['textString'], x, y, w, sample10Point['textHeight'], getSizeOnly)
543
544    if userPrefs['want12Point']:
545        y = draw_sample_row(fontName, sample12Point['fontSize'], sample12Point['lineSpace'], sample12Point['textString'], x, y, w, sample12Point['textHeight'], getSizeOnly)
546
547    if userPrefs['want16Point']:
548        y = draw_sample_row(fontName, sample16Point['fontSize'], sample16Point['lineSpace'], sample16Point['textString'], x, y, w, sample16Point['textHeight'], getSizeOnly)
549
550    if userPrefs['want20Point']:
551        y = draw_sample_row(fontName, sample20Point['fontSize'], sample20Point['lineSpace'], sample20Point['textString'], x, y, w, sample20Point['textHeight'], getSizeOnly)
552
553    if userPrefs['want32Point']:
554        y = draw_sample_row(fontName, sample32Point['fontSize'], sample32Point['lineSpace'], sample32Point['textString'], x, y, w, sample32Point['textHeight'], getSizeOnly)
555
556    if userPrefs['wantParagraph']:
557        y = draw_sample_row(fontName, sampleParagraph['fontSize'], sampleParagraph['lineSpace'], sampleParagraphText, x, y, w, sampleParagraph['textHeight'], getSizeOnly)
558
559    y = y + 1   # one extra point of space above bottom Horiz. line
560
561    lineHeight = draw_horiz_line(y, x, w + x, getSizeOnly)
562    y = y + lineHeight
563
564    return y - startPos
565
566
567def insert_toc_row(fontName, pageNum, yPos, frame):
568    """Called once for each content line to be drawn in the text frame."""
569    dotLine = ""
570    dotQuant = dD['tocCharsInRow'] - len(fontName) - len(str(pageNum)) + 1
571    for i in range(dotQuant):
572        dotLine = dotLine + '.'
573    oneLine = fontName + dotLine + str(pageNum) + "\n"
574    scribus.insertText(oneLine, yPos, frame)
575    yPos = yPos + len(oneLine) + 0
576    return yPos
577
578
579def build_toc_page_template():
580    """Inserts empty toc template into the currently selected page."""
581    # first put a header on the empty page...
582    textstring = dD['tocHeaderTitle']
583    yPos = dD['paperTopMargin'] + 1
584    header = scribus.createText(dD['paperLeftSide'], yPos, dD['paperTextWidth'], 35)
585    scribus.insertText(textstring, 0, header)
586    scribus.setFont(dD['bookstylePropFont'], header)
587    scribus.setFontSize(24, header)
588    scribus.setTextAlignment(1, header)
589    # now create a text frame for the table of contents...
590    yPos = yPos + 36
591    body = scribus.createText(dD['paperLeftSide'], yPos, dD['paperTextWidth'], dD['paperHeight'] - yPos - dD['paperBottomMargin'] - 1)
592    scribus.setFont(dD['bookstyleFixedFont'], body)
593    scribus.setFontSize(10, body)
594    scribus.setLineSpacing(12, body)
595    return body
596
597
598def build_toc(tocList):
599    """Creates all the Table of Contents pages.
600
601    Calls tocPageFramesBuild() to write the header and empty frame for the
602    toc rows each time a new page is added.
603    Then calls tocRowAdd() to add each line to the toc frame. Creates new page
604    each time it completes last row on page.
605    """
606    rowCount = 0
607    yPos = 0
608    tocPageNum = 1
609    tocPageCount = 1
610
611    scribus.newPage(tocPageNum)
612    isEvenPage = set_odd_even(tocPageNum)
613    body = build_toc_page_template()             # create frames for new empty page
614    if isEvenPage == 0:
615        scribus.setTextAlignment(2, body)
616    else:
617        scribus.setTextAlignment(0, body)
618    for i in tocList:
619        if rowCount == dD['tocRowsPerPage']: # Need to build a new TOC page (count is from zero, not one)
620            tocPageNum = tocPageNum + 1
621            scribus.newPage(tocPageNum)
622            isEvenPage = set_odd_even(tocPageNum)
623            body = build_toc_page_template()
624            if not isEvenPage:
625                scribus.setTextAlignment(2, body)
626            else:
627                scribus.setTextAlignment(0, body)
628            rowCount = 0
629            yPos = 0
630            tocPageCount = tocPageCount + 1
631        yPos = insert_toc_row(i[0], i[1], yPos, body)
632        rowCount = rowCount + 1
633    if userPrefs['wantDoubleSided']:
634        if tocPageCount % 2 != 0:           # Odd page
635            tocPageNum = tocPageNum + 1
636            scribus.newPage(tocPageNum)     # Add an extra page if odd number
637
638
639def add_page_num(pageNum):
640    yPos = dD['paperHeight'] - \
641           dD['paperBottomMargin'] - \
642           dD['paperPageNumVertOffset']
643    footer = scribus.createText(dD['paperLeftSide'], yPos, dD['paperTextWidth'], 15)
644    scribus.insertText('%s' % pageNum, 0, footer)
645    scribus.setFont(dD['bookstylePropFont'], footer)
646    scribus.setFontSize(9, footer)
647    scribus.setTextAlignment(1, footer)
648    scribus.setLineSpacing(10, footer)
649
650
651def create_empty_samplepage(pageNum, getSizeOnly):
652    """Creates a new page and increments page number by one.
653
654    Note getSizeOnly is now evaluated. Will still generate page number increment
655    but will not actually create the new page or place the number on the page."""
656    if not getSizeOnly:
657        scribus.newPage(-1)
658    pageNum = pageNum + 1
659    set_odd_even(pageNum)
660    if not getSizeOnly:
661        if userPrefs['wantPageNumbers']:
662            add_page_num(pageNum)
663    return pageNum
664
665
666def draw_horiz_line(yPos, xStart, xEnd, getSizeOnly):
667    """Draws a line and returns the height.
668
669    If getSizeOnly is set then returns the height it would have
670    used but without actually creating a line.
671    """
672    lineWidth = 1
673    if not getSizeOnly:
674        newLine = scribus.createLine(xStart, yPos, xEnd, yPos)
675        scribus.setLineWidth(lineWidth, newLine)
676    return lineWidth
677
678
679def draw_selection(fontList, getSizeOnly):
680    """Draws the sample blocks onto the Scribus canvas.
681
682    Measure one font sample block including any horizontal lines and extra
683    vertical spaces.
684    Get the amount of vertical space available for the text area between the
685    top line and top of page number area.
686    Use these two values to calculate how many complete sample blocks will fit
687    in the space available. This is the "pageBlocks"
688    Note we always draw the top horizontal line before placing the blocks. This
689    is taken into account when calculating the available text area.
690    Next, if "getSizeOnly" is false we create a page then create the sample
691    blocks while incrementing a counter until it matches the "pageBlocks".
692    Reset the counter and create new page. We keep going until we have processed
693    all the fonts in the selection list.
694    We update the Scribus progress bar as we create each font sample block.
695    The returned result is used to update some values in the status bar.
696    """
697    progress = 1
698    scribus.progressReset()
699    scribus.progressTotal(len(fontList))
700    tocList = []
701    pageNum = 1
702    blockCounter = 0
703    counter = 0
704    facingPages = scribus.NOFACINGPAGES
705
706    # Just get blocks per page value...
707    set_odd_even(pageNum)
708    lineHeight = 1 # include the one point of space below top margin
709    lineHeight = lineHeight + draw_horiz_line(0, dD['paperLeftSide'], dD['paperLeftSide'] + dD['paperTextWidth'], 1)
710    usuableArea = dD['paperHeight'] - \
711                  dD['paperTopMargin'] - \
712                  lineHeight - \
713                  dD['paperBottomMargin'] - \
714                  dD['paperPageNumVertOffset']
715
716    blockHeight = draw_sample_block(fontList[0], dD['paperLeftSide'], 0, dD['paperTextWidth'], 1)
717    pageBlocks = int(usuableArea / blockHeight)
718    #print blockHeight
719    #print "Usuable area %s points high" % usuableArea
720    #print "Used space on page is %s points high" % (blockHeight * pageBlocks)
721
722    if not getSizeOnly:
723        # not a dummy run so start by setting up page numbering...
724        if userPrefs['wantPageOneOnFirst'] and userPrefs['wantTOC']:
725            tocPageCount = divmod(len(fontList), dD['tocRowsPerPage'])
726            pageNum = pageNum + tocPageCount[0]
727            if tocPageCount[1] != 0:
728                # (adding more to page number as not whole number)
729                pageNum = pageNum + 1
730            if userPrefs['wantDoubleSided']:
731                oddEvenTest = divmod(pageNum, 2)
732                if oddEvenTest[1] == 0:
733                    # (adding extra one to start number as odd amount)
734                    pageNum = pageNum + 1
735        if userPrefs['wantDoubleSided']:
736            facingPages = scribus.FACINGPAGES
737        # now create a new document with empty page and start building...
738        scribus.newDocument(dD['paperSize'], dD['paperMargins'], scribus.PORTRAIT, 1, scribus.UNIT_POINTS, facingPages, scribus.FIRSTPAGERIGHT, 1)
739        scribus.zoomDocument(-100)
740        # A new doc gives us a new page by default so set it up first...
741        set_odd_even(pageNum)
742        yPos = dD['paperTopMargin'] + 1
743        lineHeight = draw_horiz_line(yPos, dD['paperLeftSide'], dD['paperLeftSide'] + dD['paperTextWidth'], getSizeOnly)
744        yPos = yPos + lineHeight
745        if userPrefs['wantPageNumbers']:
746            add_page_num(pageNum)
747        for i in fontList:
748            # Now place the actual sample block but create a new page if needed...
749            if counter == pageBlocks:
750                pageNum = create_empty_samplepage(pageNum, getSizeOnly)
751                yPos = dD['paperTopMargin'] + 1
752                lineHeight = draw_horiz_line(yPos, dD['paperLeftSide'], dD['paperLeftSide'] + dD['paperTextWidth'], getSizeOnly)
753                yPos = yPos + lineHeight
754                counter = 0
755            blockHeight = draw_sample_block(i, dD['paperLeftSide'], yPos, dD['paperTextWidth'], getSizeOnly)
756            yPos = yPos + blockHeight
757            # and also increment the Scribus progress bar...
758            scribus.progressSet(progress)
759            progress = progress + 1
760            # Add current font to TOC...
761            tocList.append([i, pageNum])
762            counter = counter + 1
763        if userPrefs['wantTOC']:
764            # Insert table of contents - (before page numbering)...
765            build_toc(tocList)
766        scribus.gotoPage(1)
767    return pageBlocks
768
769
770def preview_font(app, fontName):
771    """Gets the named font and puts a sample in the preview panel.
772
773    Pick up the temp sample qpixmap file and display it in a canvas object
774    The temp file is put in the users ".scribus" folder and cleaned up on exit.
775    We create samplePic as a global as a workaround because the reference count
776    does not increase when we add the image to the canvas. Therefore python
777    garbage collection removes our image before we have even displayed it.
778    Note app.previewPanel is the actual canvas.
779    """
780    global gSamplePic
781    global gPreviewId
782    scribus.renderFont(fontName, os.path.join(TEMP_PATH,'temp079r.bmp'),dD['previewpanelSampleText'],dD['previewpanelFontHeight'])
783    try:
784        tempPic = Image.open(os.path.join(TEMP_PATH,'temp079r.bmp'))
785        tempPic.save(os.path.join(TEMP_PATH,'temp079r.jpeg'),format='JPEG')
786        tempImage = Image.open(os.path.join(TEMP_PATH,'temp079r.jpeg'))
787        imgDimen = tempPic.getbbox()
788        gSamplePic = ImageTk.PhotoImage(tempImage)
789        # To center the image use "Half display height minus half the image height"
790        # preview panel is allegedly 56 (60 less a 2 pixel border top and bottom)
791        # need to be lower than that to look correct visually...
792        topEdge = (32 - (imgDimen[3] / 2))
793        gPreviewId = app.previewPanel.create_image(5, topEdge, anchor=NW, image=gSamplePic)
794        os.remove(os.path.join(TEMP_PATH,'temp079r.bmp'))
795        os.remove(os.path.join(TEMP_PATH,'temp079r.jpeg'))
796    except IOError:
797        gSamplePic = None
798        gPreviewId = app.previewPanel.create_image(0, 0, anchor=NW, image=gSamplePic)
799    return
800
801
802class AboutDialog(Toplevel):
803
804    def __init__(self, parent):
805        Toplevel.__init__(self, parent)
806        self.transient(parent)
807        self.title('About')
808        self.parent = parent
809        self.result = None
810        self.resizable(0, 0)
811
812        infoLabel = Label(self, text=WINDOW_TITLE+'\nSupport page at %s' % SUPPORT_PAGE)
813        infoLabel.pack(padx=5, pady=5)
814        # now the frame for contents...
815        contentFrame = Frame(self)
816        self.btnOk = Button(contentFrame, text='OK', command=self.ok, default=ACTIVE)
817        self.btnOk.pack(side=LEFT, padx=5, pady=5)
818        contentFrame.pack()
819        self.bind('<Return>', self.ok)
820        self.grab_set()
821        self.protocol('WM_DELETE_WINDOW', self.ok)
822        self.initial_focus = self.btnOk
823        self.wait_window(self)
824
825    def ok(self, event=None):
826        self.withdraw()
827        self.update_idletasks()
828        self.parent.focus_set()
829        self.destroy()
830
831
832class ConfigurationDialog(Toplevel):
833
834    def __init__(self, parent):
835        Toplevel.__init__(self, parent)
836        self.transient(parent)
837        self.title('Configuration')
838        self.parent = parent
839        self.result = None
840        self.resizable(0, 0)
841
842        # Create outer frame...
843        self.topFrame = Frame(self, bd=1, relief=FLAT)
844        self.topFrame.grid(row=0, column=0, padx=5, pady=5)
845
846        self.paperSizeLabel = Label(self.topFrame, text='Sample Rows:')
847        self.paperSizeLabel.grid(row=0, column=0, sticky=W)
848
849        # This frame holds each sample selector...
850        self.sampleSelectFrame = Frame(self.topFrame, bd=1, relief=RIDGE)
851        self.sampleSelectFrame.grid(row=1, column=0, padx=0, pady=2)
852
853        # now create the sample selector widgets for the frame...
854        self.__wantAlphabet = IntVar()
855        self.btnWantAlphabet = Checkbutton(self.sampleSelectFrame, text='want alphabet row', variable=self.__wantAlphabet, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
856        self.btnWantAlphabet.grid(row=0, column=0, padx=10, pady=0, sticky=W)
857        if userPrefs['wantAlphabet']:
858            self.btnWantAlphabet.select()
859
860        self.__want6Point = IntVar()
861        self.btnWant6Point = Checkbutton(self.sampleSelectFrame, text='want 6 point row', variable=self.__want6Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
862        self.btnWant6Point.grid(row=1, column=0, padx=10, pady=0, sticky=W)
863        if userPrefs['want6Point']:
864            self.btnWant6Point.select()
865
866        self.__want8Point = IntVar()
867        self.btnWant8Point = Checkbutton(self.sampleSelectFrame, text='want 8 point row', variable=self.__want8Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
868        self.btnWant8Point.grid(row=2, column=0, padx=10, pady=0, sticky=W)
869        if userPrefs['want8Point']:
870            self.btnWant8Point.select()
871
872        self.__want10Point = IntVar()
873        self.btnWant10Point = Checkbutton(self.sampleSelectFrame, text='want 10 point row', variable=self.__want10Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
874        self.btnWant10Point.grid(row=3, column=0, padx=10, pady=0, sticky=W)
875        if userPrefs['want10Point']:
876            self.btnWant10Point.select()
877
878        self.__want12Point = IntVar()
879        self.btnWant12Point = Checkbutton(self.sampleSelectFrame, text='want 12 point row', variable=self.__want12Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
880        self.btnWant12Point.grid(row=4, column=0, padx=10, pady=0, sticky=W)
881        if userPrefs['want12Point']:
882            self.btnWant12Point.select()
883
884        self.__want16Point = IntVar()
885        self.btnWant16Point = Checkbutton(self.sampleSelectFrame, text='want 16 point row', variable=self.__want16Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
886        self.btnWant16Point.grid(row=5, column=0, padx=10, pady=0, sticky=W)
887        if userPrefs['want16Point']:
888            self.btnWant16Point.select()
889
890        self.__want20Point = IntVar()
891        self.btnWant20Point = Checkbutton(self.sampleSelectFrame, text='want 20 point row', variable=self.__want20Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
892        self.btnWant20Point.grid(row=6, column=0, padx=10, pady=0, sticky=W)
893        if userPrefs['want20Point']:
894            self.btnWant20Point.select()
895
896        self.__want32Point = IntVar()
897        self.btnWant32Point = Checkbutton(self.sampleSelectFrame, text='want 32 point row', variable=self.__want32Point, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
898        self.btnWant32Point.grid(row=7, column=0, padx=10, pady=0, sticky=W)
899        if userPrefs['want32Point']:
900            self.btnWant32Point.select()
901
902        self.__wantParagraph = IntVar()
903        self.btnParagraphSelect = Checkbutton(self.sampleSelectFrame, text='want sample paragraph', variable=self.__wantParagraph, offvalue=0, onvalue=1, command=self.__sampleSelectionClick)
904        self.btnParagraphSelect.grid(row=8, column=0, padx=10, pady=0, sticky=W)
905        if userPrefs['wantParagraph']:
906            self.btnParagraphSelect.select()
907
908        self.paperSizeLabel = Label(self.topFrame, text='Paper Sizes:')
909        self.paperSizeLabel.grid(row=2, column=0, sticky=W)
910
911        self.paperSizeFrame = Frame(self.topFrame, bd=1, relief=RIDGE)
912        self.paperSizeFrame.grid(row=3, column=0, padx=0, pady=2, sticky=W)
913
914        self.__paper = StringVar()
915        self.a4papersizeSelect = Radiobutton(self.paperSizeFrame, text='A4', variable=self.__paper, value='A4', command=self.__paperSelectionClick)
916        self.a4papersizeSelect.grid(row=1, column=0, padx=10, sticky=W)
917        self.uspapersizeSelect = Radiobutton(self.paperSizeFrame, text='US Letter', variable=self.__paper, value='US Letter', command=self.__paperSelectionClick)
918        self.uspapersizeSelect.grid(row=2, column=0, padx=10, sticky=W)
919
920        # set to match prefs...
921        if userPrefs['paperSize'] == 'US Letter':
922            self.uspapersizeSelect.select()
923        if userPrefs['paperSize'] == 'A4':
924            self.a4papersizeSelect.select()
925
926        self.btnFrame = Frame(self.topFrame)
927        self.btnFrame.grid(row=4, column=0, padx=10, pady=2)
928        self.btnOk = Button(self.btnFrame, text='OK', command=self.ok)
929        self.btnOk.grid(row=2, column=0, pady=5)
930        self.bind('<Return>', self.ok)
931        self.grab_set()
932        self.initial_focus = self.btnOk
933        self.wait_window(self)
934
935
936    def __sampleSelectionClick(self):
937        """Get and store all the selections.
938
939        Just assigns the lot at once. Not worth being picky and only
940        assigning values that have changed since last time.
941        """
942        userPrefs['wantAlphabet'] = self.__wantAlphabet.get()
943        userPrefs['want6Point'] = self.__want6Point.get()
944        userPrefs['want8Point'] = self.__want8Point.get()
945        userPrefs['want10Point'] = self.__want10Point.get()
946        userPrefs['want12Point'] = self.__want12Point.get()
947        userPrefs['want16Point'] = self.__want16Point.get()
948        userPrefs['want20Point'] = self.__want20Point.get()
949        userPrefs['want32Point'] = self.__want32Point.get()
950        userPrefs['wantParagraph'] = self.__wantParagraph.get()
951        self.parent.statusbarUpdate()
952
953    def __paperSelectionClick(self):
954        userPrefs['paperSize'] = self.__paper.get()
955        self.parent.statusbarUpdate()
956
957    def ok(self, event=None):
958        dD.update(set_page_geometry(dD, geometriesList, userPrefs['paperSize'], userPrefs['wantBindingOffset']))
959        self.withdraw()
960        self.update_idletasks()
961        self.parent.focus_set()
962        self.destroy()
963
964
965class Application(Frame):
966
967    def __init__(self, master = None):
968        Frame.__init__(self, master)
969
970        self.grid()
971
972        # Remove maximise button and resize. Not good to allow resizable window
973        # because the listboxes are fixed width...
974        self.master.resizable(0, 0)
975
976        # build the menu...
977        menubar = Menu(self)
978        settingsmenu = Menu(menubar, tearoff=0)
979        settingsmenu.add_command(label='Configuration', command=self.__configurationDlgShow)
980        settingsmenu.add_separator()
981        settingsmenu.add_command(label='Save current settings', command=self.__saveCurrentSettingsAsDefaults)
982        settingsmenu.add_command(label='Load saved settings', command=self.__restoreCurrentSettingsFromDefault)
983
984        menubar.add_cascade(label='Settings', menu=settingsmenu)
985        helpmenu = Menu(menubar, tearoff=0)
986        helpmenu.add_command(label='About', command=self.__aboutDlgShow)
987        menubar.add_cascade(label='Help', menu=helpmenu)
988        # display menu...
989        self.master.config(menu=menubar)
990
991        # now start adding our widgets starting with the top frame...
992        self.listbox_frame = Frame(self)
993        self.listbox_frame.grid(row=0, column=0, sticky=EW)
994
995        # left hand listbox assembly
996        self.leftListbox_frame = Frame(self.listbox_frame, borderwidth=1, relief=SUNKEN)
997        self.leftListbox_frame.grid(row=1, column=0)
998
999        self.leftLabel = Label(self.listbox_frame, text='Available Fonts')
1000        self.leftLabel.grid(row=0, column=0, sticky=NS)
1001
1002        self.yScroll1 = Scrollbar(self.leftListbox_frame, orient=VERTICAL)
1003        self.yScroll1.grid(row=0, column=1, sticky=NS)
1004        self.xScroll1 = Scrollbar(self.leftListbox_frame, orient=HORIZONTAL)
1005        self.xScroll1.grid(row=1, column=0, sticky=EW)
1006
1007        self.listbox1 = Listbox(self.leftListbox_frame,
1008            xscrollcommand=self.xScroll1.set,
1009            yscrollcommand=self.yScroll1.set,
1010            selectmode=EXTENDED,
1011            height=20, width=40)
1012        self.listbox1.grid(row=0, column=0, sticky=NSEW)
1013        self.xScroll1['command'] = self.listbox1.xview
1014        self.yScroll1['command'] = self.listbox1.yview
1015
1016        def __listbox1KeyRelease(event):
1017            """Check if an Up or Down key has been pressed and released and
1018            if so the preview panel is refreshed. If the keys are held down
1019            the file system slows the scroll. Need a timer here to delay
1020            updates."""
1021            if (event.keysym == 'Down' or event.keysym == 'Up'):
1022                __listbox1DoLogicCallback(self)
1023
1024        def __listbox1SingleClick(event):
1025            """Call this first when listbox1 is clicked with mouse to put focus
1026            into the listbox. Note we call when mouse click is released, not pressed,
1027            due to the fact that the listbox does not change the selection until the button
1028            is released."""
1029            self.listbox1.focus_set()
1030            __listbox1DoLogicCallback(self)
1031        self.listbox1.bind('<ButtonRelease-1>', __listbox1SingleClick)
1032
1033        def __listbox1DoLogicCallback(event):
1034            """Decides if current selection should be previewed.
1035
1036            Start by counting items in selection list and if equal to one then
1037            show selected font, ignoring if more or less than one. Then also
1038            set up buttons logic depending on selection. We bind the FocusIn
1039            to this too so button logic and preview gets updated when focus
1040            enters the listbox.
1041            """
1042            # note we are not making use of "self.listbox1.get(ACTIVE)" due to
1043            # it not getting the real active name. Always one selection behind
1044            # even though we are doing all this in the ButtonRelease event.
1045            # Have made a change here. If more than one font name is selected
1046            # then we just empty the preview.
1047            names = self.listbox1.curselection()
1048            if len(names) == 1:
1049                selectedFont = self.listbox1.get(names[0])
1050                self.__curSelectedItem(selectedFont)
1051            else:
1052                try:
1053                    app.previewPanel.delete(previewId)
1054                except:
1055                    pass
1056            #else:
1057                #selectedFont = self.listbox1.get(ACTIVE)
1058                #print selectedFont  # for testing
1059                #if selectedFont != "":
1060                    #self.__curSelectedItem(selectedFont)
1061
1062            # Now do the button logic...
1063            self.listbox2.selection_clear(0,END)
1064            self.__setUpDownActive(0, 0)    # Force a disable if in other box
1065            if self.listbox1.size() > 0:
1066                self.__setSelButtonsActive(0, 1)
1067            else:
1068                self.__setSelButtonsActive(0, 0)
1069
1070        self.listbox1.bind('<FocusIn>', __listbox1DoLogicCallback)
1071        self.listbox1.bind('<Any-KeyRelease>', __listbox1KeyRelease)
1072
1073        def __listbox1DoubleClickCallback(event):
1074            """The single click event will fire also when left listbox
1075            is double clicked but we are detecting the single click button up event."""
1076            self.__listSelectionToRight()
1077
1078        self.listbox1.bind('<Double-Button-1>', __listbox1DoubleClickCallback)
1079
1080        # middle button frame assembly
1081        self.midbutton_frame = Frame(self.listbox_frame)
1082        self.midbutton_frame.grid(row=0, rowspan=2, column=1, sticky=NSEW)
1083
1084        self.rsingleButton = Button(self.midbutton_frame, state='disabled', text='>', command=self.__rsingleButtonClick)
1085        self.rsingleButton.grid(row=0, column=0, padx=5, pady=5, sticky=EW)
1086        self.rdoubleButton = Button(self.midbutton_frame, text='>>', command=self.__rdoubleButtonClick)
1087        self.rdoubleButton.grid(row=1, column=0, padx=5, pady=5, sticky=EW)
1088
1089        self.itemupButton = Button(self.midbutton_frame, state='disabled', text='Up', command=self.__itemupButtonClick)
1090        self.itemupButton.grid(row=2, column=0, padx=5, pady=5, sticky=EW)
1091        self.itemdownButton = Button(self.midbutton_frame, state='disabled', text='Down', command=self.__itemdownButtonClick)
1092        self.itemdownButton.grid(row=3, column=0, padx=5, pady=5, sticky=EW)
1093
1094        self.lsingleButton = Button(self.midbutton_frame, state='disabled', text='<', command=self.__lsingleButtonClick)
1095        self.lsingleButton.grid(row=4, column=0, padx=5, pady=5, sticky=EW)
1096        self.ldoubleButton = Button(self.midbutton_frame, state='disabled', text='<<', command=self.__ldoubleButtonClick)
1097        self.ldoubleButton.grid(row=5, column=0, padx=5, pady=5, sticky=EW)
1098
1099        # Right hand listbox assembly
1100        self.rightListbox_frame = Frame(self.listbox_frame, borderwidth=1, relief=SUNKEN)
1101        self.rightListbox_frame.grid(row=1, column=2)
1102
1103        self.rightLabel = Label(self.listbox_frame, text='Selected Fonts')
1104        self.rightLabel.grid(row=0, column=2, sticky=NS)
1105
1106        self.yScroll2 = Scrollbar(self.rightListbox_frame, orient=VERTICAL)
1107        self.yScroll2.grid(row=0, column=1, sticky=NS)
1108        self.xScroll2 = Scrollbar(self.rightListbox_frame, orient=HORIZONTAL)
1109        self.xScroll2.grid(row=1, column=0, sticky=EW)
1110
1111        self.listbox2 = Listbox(self.rightListbox_frame,
1112            xscrollcommand=self.xScroll2.set,
1113            yscrollcommand=self.yScroll2.set,
1114            selectmode=EXTENDED,
1115            height=20, width=40)
1116        self.listbox2.grid(row=0, column=0, sticky=NSEW)
1117        self.xScroll2['command'] = self.listbox2.xview
1118        self.yScroll2['command'] = self.listbox2.yview
1119
1120        def __listbox2SingleClick(event):
1121            """Similar to __listbox1SingleClick()."""
1122            self.listbox2.focus_set()
1123            __listbox2DoLogicCallback(self)
1124        self.listbox2.bind('<ButtonRelease-1>', __listbox2SingleClick)
1125
1126        def __listbox2KeyRelease(event):
1127            if (event.keysym == 'Down' or event.keysym == 'Up'):
1128                __listbox2DoLogicCallback(self)
1129
1130        def __listbox2DoLogicCallback(event):
1131            """Similar to __listbox1DoLogicCallback()."""
1132            names = self.listbox2.curselection()
1133            if len(names) == 1:
1134                selectedFont = self.listbox2.get(names[0])
1135                self.__curSelectedItem(selectedFont)
1136            else:
1137                try:
1138                    app.previewPanel.delete(previewId)
1139                except:
1140                    pass
1141
1142            # Now do the button logic...
1143            self.listbox1.selection_clear(0,END)
1144            self.__testUpDownState()
1145            if self.listbox2.size() > 0:
1146                self.__setSelButtonsActive(1, 0)
1147            else:
1148                self.__setSelButtonsActive(0, 0)
1149        self.listbox2.bind('<FocusIn>', __listbox2DoLogicCallback)
1150        self.listbox2.bind('<Any-KeyRelease>', __listbox2KeyRelease)
1151
1152        def __listbox2DoubleClickCallback(event):
1153            """Similar to __listbox1DoubleClickCallback()."""
1154            self.__listSelectionToLeft()
1155        self.listbox2.bind('<Double-Button-1>', __listbox2DoubleClickCallback)
1156
1157        # now draw the bottom font preview frame if required...
1158        if showPreviewPanel:
1159            self.preview_frame = Frame(self)
1160            self.preview_frame.grid(row=1, column=0, sticky=EW)
1161            self.previewPanel = Canvas(self.preview_frame, height=60, bg='white', bd=2, relief=SUNKEN)
1162            self.previewPanel.pack(fill=X)
1163
1164        # now draw the bottom controls frame...
1165        self.controls_frame = Frame(self)
1166        self.controls_frame.grid(row=2, column=0, sticky=EW)
1167
1168        # create a container...
1169        self.button_frame1 = Frame(self.controls_frame, bd=1, relief=RIDGE)
1170        self.button_frame1.grid(row=0, column=0, padx=10, pady=2)
1171        # create and add page number selection button...
1172        self.__wantPageNum = IntVar()
1173        self.pagenumSelect = Checkbutton(self.button_frame1, text='Print page numbers', variable=self.__wantPageNum, offvalue=0, onvalue=1, command=self.__pageNumberSelectButtonClick)
1174        self.pagenumSelect.grid(row=0, column=0, padx=0, sticky=W)
1175
1176        # create a container...
1177        self.button_frame2 = Frame(self.controls_frame, bd=1, relief=RIDGE)
1178        self.button_frame2.grid(row=0, column=1, padx=10, pady=2)
1179        # create and add the TOC selector...
1180        self.__wantToc = IntVar()
1181        self.tocSelect = Checkbutton(self.button_frame2, text='Print table of contents', variable=self.__wantToc, offvalue=0, onvalue=1, command=self.__tocSelectButtonClick)
1182        self.tocSelect.grid(row=0, column=0, padx=0, sticky=W)
1183        # create and add page one on first selector...
1184        self.__wantPageOneOnFirst = IntVar()
1185        self.btnPageOneOnFirst = Checkbutton(self.button_frame2, text='Page count includes TOC', variable=self.__wantPageOneOnFirst, offvalue=0, onvalue=1, command=self.__wantPageOneOnFirstClick)
1186        self.btnPageOneOnFirst.grid(row=1, column=0, padx=0, sticky=W)
1187
1188        # create a container...
1189        self.button_frame3 = Frame(self.controls_frame, bd=1, relief=RIDGE)
1190        self.button_frame3.grid(row=0, column=2, padx=10, pady=2)
1191        # create and add the binding offset...
1192        self.__wantBindingOffset = IntVar()
1193        self.bindingoffsetSelect = Checkbutton(self.button_frame3, text='Extra offset for binding', variable=self.__wantBindingOffset, offvalue=0, onvalue=1, command=self.__bindingoffsetSelectButtonClick)
1194        self.bindingoffsetSelect.grid(row=0, column=0, sticky=W)
1195        # create and add the double sided selection buttons...
1196        self.__wantDoubleSided = IntVar()
1197        self.doublesidedSelect = Checkbutton(self.button_frame3, text='Double sided pages', variable=self.__wantDoubleSided, offvalue=0, onvalue=1, command=self.__doubleSidedSelectButtonClick)
1198        self.doublesidedSelect.grid(row=1, column=0, rowspan=2, sticky=W)
1199
1200        # now the ok and cancel buttons...
1201        self.cancelButton = Button(self.controls_frame, text='Cancel', command=self.__cancelButtonClick)
1202        self.cancelButton.grid(row=0, column=3, padx=5)
1203        self.okButton = Button(self.controls_frame, text='OK', state='disabled', command=self.__okButtonClick)
1204        self.okButton.grid(row=0, column=4, padx=5)
1205
1206        # now create the bottom status bar frame and contents...
1207        self.status_frame = Frame(self)
1208        self.status_frame.grid(row=3, column=0, sticky=E+W)
1209        self.status0 = Label(self.status_frame, bd=1, relief=SUNKEN, anchor=W)
1210        self.status0.pack(side=LEFT)
1211        self.status1 = Label(self.status_frame, bd=1, relief=SUNKEN, anchor=W)
1212        self.status1.pack(side=LEFT)
1213        self.status2 = Label(self.status_frame, bd=1, relief=SUNKEN, anchor=W)
1214        self.status2.pack(side=LEFT)
1215        self.status3 = Label(self.status_frame, bd=1, relief=SUNKEN, anchor=W)
1216        self.status3.pack(side=LEFT)
1217        self.statusPaperSize = Label(self.status_frame, bd=1, relief=SUNKEN, anchor=W)
1218        self.statusPaperSize.pack(fill=X)
1219
1220    def statusbarUpdate(self):
1221        """Draws the status bar contents.
1222
1223        Note "draw_selection()" does a dummy run to count the amount of sample
1224        blocks on a sheet.
1225        TODO: Statusbar setting and recalculation should be separated. Just recalc
1226        and refresh panels as required instead of all of them each time.
1227        """
1228        available = self.listbox1.size()
1229        selected = self.listbox2.size()
1230        size = float(selected)
1231        blocksPerSheet = draw_selection(scribus.getFontNames(), 1)
1232        value = size / blocksPerSheet
1233        pages = int(value)                  # Get whole part of number
1234        value = value - pages                   # Remove whole number part
1235        if value > 0:                           # Test remainder
1236            pages = pages + 1                   # Had remainder so add a page
1237        self.status0['text'] = 'Fonts available: %s   ' % (available + selected)
1238        self.status1['text'] = 'Fonts selected: %s   ' % selected
1239        self.status2['text'] = 'Sheets required: %s   ' % pages
1240        self.status3['text'] = 'Fonts per sheet: %s   ' % blocksPerSheet
1241        self.statusPaperSize['text'] = 'Paper size: %s   ' % userPrefs['paperSize']
1242
1243    def __listSelectionToRight(self):
1244        toMoveRight = list(self.listbox1.curselection())
1245        self.listbox1.selection_clear(0,END)
1246        toMoveRight.reverse()   # reverse list so we delete from bottom of listbox first
1247        tempList = []
1248        for i in toMoveRight:
1249            tempList.insert(0,self.listbox1.get(i)) # gets the actual strings (reverse again)
1250            self.listbox1.delete(i)
1251        for j in tempList:
1252            self.listbox2.insert(END, j)
1253        self.__setButtonsState()
1254        self.__setSelButtonsActive(0, 0)
1255        self.statusbarUpdate()
1256
1257    def __listSelectionToLeft(self):
1258        toMoveLeft = list(self.listbox2.curselection())
1259        toMoveLeft.reverse()
1260        self.listbox2.selection_clear(0,END)
1261        for i in toMoveLeft:
1262            self.listbox1.insert(END, self.listbox2.get(i)) # Insert it at the end
1263            self.listbox2.delete(i)
1264        fontList = list(self.listbox1.get(0, END))      # Copy contents to a list type
1265        self.listbox1.delete(0, END)                        # Remove all contents
1266        fontList.sort()                                     # Use sort method of list
1267        for j in fontList:
1268            self.listbox1.insert(END, j)                    # Replace with sorted version
1269        self.__setButtonsState()
1270        self.__setSelButtonsActive(0, 0)
1271        self.statusbarUpdate()
1272
1273    def __listAllToRight(self):
1274        fontList = self.listbox1.get(0, END)    # Get each font name into a list
1275        for i in fontList:
1276            self.listbox2.insert(END, i)        # Copy each one
1277        self.listbox1.delete(0, END)            # All done so empty the left listbox
1278        self.__setButtonsState()
1279        self.__setSelButtonsActive(0, 0)
1280        self.statusbarUpdate()
1281
1282    def __listAllToLeft(self):
1283        """Moves all selected fonts back to the left hand pane.
1284
1285        Note we just clear both panes then reload the left listbox in correct
1286        order from scratch as this is probably quicker than moving each
1287        item individually.
1288        """
1289        self.listbox1.delete(0, END)
1290        fontList = scribus.getFontNames()
1291        fontList.sort()
1292        for i in fontList:
1293            self.listbox1.insert(END, i)
1294        self.listbox2.delete(0, END)
1295        self.__setButtonsState()
1296        self.__setSelButtonsActive(0, 0)
1297        self.statusbarUpdate()
1298
1299    def __setSelButtonsActive(self, selToRight, selToLeft):
1300        # The "selection" buttons are the ones with ">"  and "<"  on them
1301        if selToRight == 1:
1302            self.lsingleButton['state'] = NORMAL
1303        else:
1304            self.lsingleButton['state'] = DISABLED
1305        if selToLeft == 1:
1306            self.rsingleButton['state'] = NORMAL
1307        else:
1308            self.rsingleButton['state'] = DISABLED
1309
1310    def __setAllButtonsActive(self, allToRight, allToLeft):
1311        # The "all" buttons are the ones with ">>"  and "<<"  on them
1312        if allToRight == 1:
1313            self.rdoubleButton['state'] = NORMAL
1314        else:
1315            self.rdoubleButton['state'] = DISABLED
1316        if allToLeft == 1:
1317            self.ldoubleButton['state'] = NORMAL
1318        else:
1319            self.ldoubleButton['state'] = DISABLED
1320
1321    def __setButtonsState(self):
1322        if self.listbox2.size() > 0 and self.listbox1.size() > 0:
1323            self.__setAllButtonsActive(1, 1)
1324            self.okButton['state'] = NORMAL
1325        elif self.listbox2.size() == 0:
1326            self.__setAllButtonsActive(1, 0)
1327            self.okButton['state'] = DISABLED
1328        elif self.listbox1.size() == 0:
1329            self.__setAllButtonsActive(0, 1)
1330            self.okButton['state'] = NORMAL
1331
1332    def __itemUp(self):
1333        """Test if one item is selected then move it up one position."""
1334        selection = self.listbox2.curselection()
1335        if len(selection) == 1:
1336            indexId = IntType(selection[0]) # Get the first (only) item as integer type
1337            if indexId > 0:
1338                fontString = self.listbox2.get(indexId)
1339                self.listbox2.delete(indexId)
1340                newPos = indexId - 1
1341                self.listbox2.selection_clear(0, END)
1342                self.listbox2.insert(newPos, fontString)
1343                self.listbox2.see(newPos - 10)  # Scrolls listbox automatically into view if req.
1344                self.listbox2.selection_set(newPos)
1345                self.listbox2.activate(newPos)  # make focus follow selection
1346                self.__testUpDownState()  # note tests only after an item has been moved
1347
1348    def __itemDown(self):
1349        """Test if one item is selected then move it down one position."""
1350        limit = self.listbox2.size()
1351        selection = self.listbox2.curselection()
1352        if len(selection) == 1:
1353            indexId = IntType(selection[0])
1354            if indexId < limit - 1:
1355                fontString = self.listbox2.get(indexId)
1356                self.listbox2.delete(indexId)
1357                newPos = indexId + 1
1358                self.listbox2.selection_clear(0, END)
1359                self.listbox2.insert(newPos, fontString)
1360                self.listbox2.see(newPos + 10)
1361                self.listbox2.selection_set(newPos)
1362                self.listbox2.activate(newPos)  # make focus follow selection
1363                self.__testUpDownState()
1364
1365    def __setUpDownActive(self, up, down):
1366        """Just sets the buttons active or inactive.
1367
1368        See testUpDown() for the actual evaluation
1369        """
1370        if up == 1:
1371            self.itemupButton['state'] = NORMAL
1372        else:
1373            self.itemupButton['state'] = DISABLED
1374        if down == 1:
1375            self.itemdownButton['state'] = NORMAL
1376        else:
1377            self.itemdownButton['state'] = DISABLED
1378
1379    def __testUpDownState(self):
1380        """Only enable the up and down buttons when just a single item is selected.
1381
1382        Enable should be applied to up, down or both depending on its
1383        position in the listbox. At all other times disable both.
1384        """
1385        # Get a count of how many items are currently selected...
1386        selection = list(self.listbox2.curselection())
1387        tcount = 0
1388        for sel in selection:
1389            tcount = tcount + 1
1390
1391        position = 0
1392        if tcount == 1:
1393            position = IntType(selection[0])
1394
1395        # If one selected and there is more than one item in the listbox then ok...
1396        if tcount == 1 and self.listbox2.size() > 1:
1397            # Now test the position of the selected line...
1398            if position > 0 and position < self.listbox2.size() - 1:    # Both
1399                self.__setUpDownActive(1, 1)
1400            # If not one less or lesser from the bottom (listbox.count-1?) then gray the down button.
1401            elif position == self.listbox2.size() - 1:                  # Up only
1402                self.__setUpDownActive(1, 0)
1403            # If not one or more from the top then gray up button.
1404            elif position == 0:                                         # Down only
1405                self.__setUpDownActive(0, 1)
1406        else:
1407            self.__setUpDownActive(0, 0)
1408
1409    def __curSelectedItem(self, selectedFont):
1410        """Send the selected font to the preview function if preview available."""
1411        if showPreviewPanel:
1412            preview_font(self, selectedFont)
1413
1414    # create the button events...
1415    def __rsingleButtonClick(self):
1416        self.__listSelectionToRight()
1417
1418    def __rdoubleButtonClick(self):
1419        self.__listAllToRight()
1420
1421    def __lsingleButtonClick(self):
1422        self.__listSelectionToLeft()
1423        self.__testUpDownState()
1424
1425    def __ldoubleButtonClick(self):
1426        self.__listAllToLeft()
1427        self.__testUpDownState()
1428
1429    def __itemupButtonClick(self):
1430        self.__itemUp()
1431
1432    def __itemdownButtonClick(self):
1433        self.__itemDown()
1434
1435    def __tocSelectButtonClick(self):
1436        userPrefs['wantTOC'] = self.__wantToc.get()
1437        if userPrefs['wantTOC']:
1438            self.btnPageOneOnFirst['state'] = NORMAL
1439        else:
1440            self.btnPageOneOnFirst['state'] = DISABLED
1441
1442    def __pageNumberSelectButtonClick(self):
1443        userPrefs['wantPageNumbers'] = self.__wantPageNum.get()
1444
1445    def __bindingoffsetSelectButtonClick(self):
1446        userPrefs['wantBindingOffset'] = self.__wantBindingOffset.get()
1447        dD.update(set_page_geometry(dD, geometriesList, userPrefs['paperSize'], userPrefs['wantBindingOffset']))
1448
1449    def __doubleSidedSelectButtonClick(self):
1450        userPrefs['wantDoubleSided'] = self.__wantDoubleSided.get()
1451
1452    def __wantPageOneOnFirstClick(self):
1453        userPrefs['wantPageOneOnFirst'] = self.__wantPageOneOnFirst.get()
1454
1455    def __cancelButtonClick(self):
1456        """We exit the app here if user presses cancel."""
1457        self.master.destroy()
1458
1459    def __okButtonClick(self):
1460        """User presses ok, so lets create the pages."""
1461        save_user_conf(CONFIG_PATH)
1462        draw_selection(self.listbox2.get(0, END), 0)
1463        self.master.destroy()
1464
1465    def __configurationDlgShow(self):
1466        """Opens the configuration dialog where user can set up the options"""
1467        configs = ConfigurationDialog(self)
1468        self.statusbarUpdate()
1469
1470    def __saveCurrentSettingsAsDefaults(self):
1471        """Stores current settings as defaults."""
1472        defaultPrefs.update(userPrefs)
1473        save_user_conf(CONFIG_PATH)
1474
1475    def __restoreCurrentSettingsFromDefault(self):
1476        """Restores current settings from defaults."""
1477        userPrefs.update(defaultPrefs)
1478        self.initialiseWidgets()
1479        self.statusbarUpdate()
1480
1481    def initialiseWidgets(self):
1482        if userPrefs['wantPageNumbers']:
1483            self.pagenumSelect.select()
1484        else:
1485            self.pagenumSelect.deselect()
1486        if userPrefs['wantTOC']:
1487            self.tocSelect.select()
1488            self.btnPageOneOnFirst['state'] = NORMAL
1489        else:
1490            self.tocSelect.deselect()
1491            self.btnPageOneOnFirst['state'] = DISABLED
1492        if userPrefs['wantBindingOffset']:
1493            self.bindingoffsetSelect.select()
1494        else:
1495            self.bindingoffsetSelect.deselect()
1496        if userPrefs['wantDoubleSided']:
1497            self.doublesidedSelect.select()
1498        else:
1499            self.doublesidedSelect.deselect()
1500        if userPrefs['wantPageOneOnFirst']:
1501            self.btnPageOneOnFirst.select()
1502        else:
1503            self.btnPageOneOnFirst.deselect()
1504
1505    def __aboutDlgShow(self):
1506        """Brings up a dialog with support url etc."""
1507        about = AboutDialog(self)
1508
1509
1510def setup_tk():
1511    """Create and setup the tkinter app."""
1512    root = Tk()
1513    app = Application(root)
1514    app.master.title(WINDOW_TITLE)
1515
1516    # now get a list of all the fonts Scribus knows about...
1517    fontList = scribus.getFontNames()
1518    fontList.sort()
1519    # and load the list into the GUI listbox...
1520    for i in fontList:
1521        app.listbox1.insert(END, i)
1522    app.sampleBlocksPerPage = draw_selection(scribus.getFontNames(), 1)
1523    # now set the status bar message...
1524    app.statusbarUpdate()
1525    # set up widgets using data from userPrefs...
1526    app.initialiseWidgets()
1527    return app
1528
1529def initialisation():
1530    """Test for suitable fonts and on success creates tkinter app."""
1531    try:
1532        dD['bookstyleFixedFont'] = set_font_fixed(fontsListFixed)
1533        dD['bookstylePropFont'] = set_font_proportional(fontsListProportional)
1534    except:
1535        scribus.messageBox('Font problem',
1536                           '%s' % sys.exc_info()[1],
1537                           scribus.ICON_WARNING)
1538        sys.exit(1)
1539    # load users saved defaults...
1540    restore_user_conf(CONFIG_PATH)
1541    # get and set the initial paper size to match default radiobutton selection...
1542    dD.update(set_page_geometry(dD, geometriesList, userPrefs['paperSize'], userPrefs['wantBindingOffset']))
1543    # Made it this far so its time to create our tkinter app...
1544    app = setup_tk()
1545    # now show the main window and wait for user to do something...
1546    app.mainloop()
1547
1548
1549def main(argv):
1550    """Application initialization, font checks and initial setup."""
1551    initialisation()
1552
1553
1554def main_wrapper(argv):
1555    """The main_wrapper() function disables redrawing, sets a sensible generic
1556    status bar message, and optionally sets up the progress bar. It then runs
1557    the main() function. Once everything finishes it cleans up after the main()
1558    function, making sure everything is sane before the script terminates."""
1559    try:
1560        scribus.statusMessage('Running script...')
1561        scribus.progressReset()
1562        main(argv)
1563    finally:
1564        # Exit neatly even if the script terminated with an exception,
1565        # so we leave the progress bar and status bar blank and make sure
1566        # drawing is enabled.
1567        if scribus.haveDoc() > 0:
1568            scribus.setRedraw(True)
1569        scribus.statusMessage('')
1570        scribus.progressReset()
1571
1572
1573# This code detects if the script is being run as a script, or imported as a module.
1574# It only runs main() if being run as a script. This permits you to import your script
1575# and control it manually for debugging.
1576if __name__ == '__main__':
1577    main_wrapper(sys.argv)
1578
1579