Bullets and numbered lists

From Scribus Wiki
Revision as of 04:32, 16 January 2011 by Meira (talk | contribs) (→‎Usage)
Jump to navigation Jump to search
This article is part of the Scripts series.

Improved bullets simple script using regular expressions Python module. Build bulleted lists, numbered lists starting with any number plus separator and delete list marks. Create styles -- if it doesn't exists -- with proper 2 digits indentation, but is necessary set tabulation "by hand", as this can't be done by script.

Usage

Select a frame or some paragraphs and run the script. Asked, input the operation type:

  • "*" will create bulleted list.
  • A number (up 3 digit) will create a numbered list begining with such number. Ex.: "1)"
  • One or two letters, uppper or lower case, will create a lettered list begining with such letters with same case. Ex.: "a)"
  • "/" will clear all marks.
  • An optional character after numbers or letters will be used as separator. Default: dot.

Caveats

Maybe will FREEZE Scribus in Ubuntu 10.04 and 10.10, as script use Pyhton module (re, regular expressions). This is a Ubuntu + Scribus Scripter bug.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Insert marks, numbers or letters in front selected paragraphs.
Author: prof. MS. José Antonio Meira da Rocha
mailto:joseantoniomeira@gmail.com
http://meiradarocha.jor.br
License GPL 2.0
2011-01-15a

Usage:
======
Select paragraphs at any points. Dialog will ask mark type.
Input: 
* "*" will create bulleted list.
* A number (up 3 digit) will create a numbered list begining with such number. Ex.: "1)"
* One or two letters uppper or lower case will create a lettered list begining with such letters. Ex.: "a)"
* "/" will clear all marks.
* An optional character after numbers or letters will be used as separator. Default: dot.

Caveats:
* Cleans all text format of text selection (font, bold, subscript, etc).
* Creates paragraph style "Numbered List", "Lettered list" or "Bullet", if it doesn't exist, with proper indentation. Tabulation need to be defined by user to identical identation value.
* If there is no selection, script will apply marks to all text in current frame.
"""

import sys
import re

try:
    import scribus
except ImportError,err:
    print "This Python script is written for the Scribus scripting interface."
    print "It can only be run from within Scribus."
    sys.exit(1)
from scribus import BUTTON_OK, ICON_WARNING

#######################
# Change this variables
# to match your needs
#######################
numberedListStyle = 'Numbered list'
letteredListStyle = 'Lettered list'
bulletStyle = 'Bullet'
defaultStyle = 'Default Paragraph Style'
separator = '.'
indent = 22 # points
# Put your favorite unicode char and tab: '\u2022\t' 
# Get hex code from "Insert > Special character..." palette
bulletString = u'•\t'

###########
# Locales
###########

# Message to ask frame selection
scriptWindowTitle = "Format list"
askSelectText = "<h2>Select a text</h2>\n" \
        +"Select a <b>story text</b> where you want insert numbers."
# Message to open a document
askOpenDoc = "<h2>Open a document</h2>" \
        +"Open a document befora run this script."
# Message asking select more text
askSelectMoreText = "<h2>Select more text</h2>" \
        +"Not unique text selected. Select more text before run this script."
wrongFrameTypeErrorMsg = "<h2>Wrong object selected</h2>" \
        +"You must select a <b>text frame</b>."

markOperation = ''
bulletOperation = '*'
numberOperation = '1'
cleanOperation = '/'
alphaOperation = 'Aa'
defaultOperation = bulletOperation
alphabet = 'abcdefghijklmnopqrstuvwxyz'
alphabetLen = len(alphabet)

askOperationMsg = '<h2>What mark operation?</h2>' \
        +'<ul><li>"<b>'+bulletOperation.lower()+'</b>" input will create <b>bullet</b> marks.' \
        +'and apply <i>'+bulletStyle+'</i>  style.' \
        +'<li>A <b>number</b> input will create <b>numbered list</b>  starting <br>with such number and apply <i>'+numberedListStyle+'</i>  style.' \
        +'<li>One or two <b>letters</b> input, upper or lower case, <br>will create <b>lettered list</b> starting with such letters,<br>' \
        +'and apply <i>'+letteredListStyle+'</i>  style.' \
        +'<li><font color="#0000ff">Optional char after number or letter<br> will be used as separator. Default: dot. Ex.:  "<b>a)</b>"</font>' \
        +'<li>"<b>'+cleanOperation.lower()+'</b>" input will <b>delete</b> marks ' \
        +'and apply <br><i>'+defaultStyle+'</i>  style.</li>' \
        +'<li>Any <b>other</b> input will <b>cancel</b> all operations.</li></ul>' \

###### No need to change after this line #############

# Regular expression to match any mark:
#  '\r' = carriage return in Python
#  '\t' = tabulation in Python
#  [^\t|^\r] = non-tab or non-CR chars
#  {,4} = 0 to 4 ocurrencies from paragraph begin to tab
reAnyMark  = r'\r[^\t|^\r]{,4}\t' 

##############
# Apply style
def applyStyle(style,frame):
    '''Apply a style in selected text. 
    If style doesn't exist, create it.'''
    try:
        scribus.setStyle(style,frame)
    except:
        scribus.createParagraphStyle(name=style,leftmargin=indent,firstindent=(indent*-1))
        scribus.setStyle(style,frame)
    return frame

##########################
# Get text cursor position
def getTextCursor(story):
    '''Find position of text cursor in text frame.
    Text need to be selected.'''
    selectedFrame = scribus.getSelectedObject()
    try:
        selectedText = scribus.getText()
    except:
        scribus.messageBox(scriptWindowTitle,wrongFrameTypeErrorMsg,ICON_WARNING,BUTTON_OK)
        scribus.selectObject(story)
        sys.exit()
    scribus.deselectAll()
    allText = scribus.getAllText(selectedFrame)
    
    if allText.count(unicode(selectedText)) == 1:
        cur = allText.find(unicode(selectedText))
    else:
        scribus.messageBox(scriptWindowTitle,askSelectMoreText,ICON_WARNING,BUTTON_OK)
        scribus.selectObject(story)
        sys.exit(1)
    
    return cur,allText,selectedText

############
# Numbering
num = 0
def numbering(matchObj):
    '''Function to re.sub use.'''
    global num
    # Define limit to list
    limit = 999
    if num > limit: num = limit
    s = u'\r'+str(num)+separator+'\t'
    num += 1
    return s

####################
# Lettering
def lettering(matchObj):
    '''Function to re.sub use.'''
    ######## bugged ################
    global num
    # Define limit to list (zz)
    limit = ((alphabetLen+1) * alphabetLen)
    if num > limit: num = limit
    d1 = ((num-1) % alphabetLen)
    s1 = alphabet[d1]
    d2 = ((num-1) / alphabetLen)-1
    if d2 > -1:
        s2 = alphabet[d2]
        s1 = s2+s1
    s = u'\r'+s1+separator+'\t'
    if markOperation.isupper():
        s = s.upper()
    num += 1
    return s

####################
def insertMarks(t):
    '''Delete old marks and insert new ones.'''
    # Clear old numbers
    s = re.sub(reAnyMark,u'\r',t)
    if markOperation == numberOperation:
        # Insert numbers
        s = re.sub(u'\r',numbering,s)
    elif markOperation.isalpha():
        # Insert letters
        s = re.sub(u'\r',lettering,s)
    elif markOperation == bulletOperation:
        # Insert bullets
        b = u'\r'+bulletString
        s = re.sub(u'\r',b,s)
    
    return s

#################
# Do all the job
def doAllOperation(story):
    '''Insert marks in front selected paragraphs or clean marks.'''
    # Get selection begin
    cur,allText,selectedText = getTextCursor(story)
    # Get first selected paragraph begin
    # (last line break position after selection)
    begin = allText.rfind(u'\r',0,cur)+1 # Scribus not use '\n'?
    # Get selection end
    end = cur+len(unicode(selectedText))
    # Get last selected paragraph end
    end = allText.find(u'\r',end)
    totalLen = end - begin
    scribus.selectText(begin,totalLen,story)
    selectedText = scribus.getText(story)
    #print '\nSelected:\n',repr(selectedText.encode('utf-8')) # debug
    # Put temporary carriage return to allow numbering at string begin
    selectedText = u'\r'+selectedText
    # Clean old numbers and insert new ones
    cleanedText = insertMarks(unicode(selectedText))
    # Strip temp initial CR
    cleanedText = unicode(cleanedText)[1:]
    scribus.deleteText(story)
    scribus.insertText(cleanedText,begin,story)
    # Apply style
    scribus.deselectAll()
    # Get selection new end
    end = len(unicode(cleanedText))
    scribus.selectText(begin,end,story)
    if markOperation == bulletOperation:
        applyStyle(bulletStyle,story)
    elif markOperation == numberOperation:
        applyStyle(numberedListStyle,story)
    elif markOperation == cleanOperation:
        applyStyle(defaultStyle,story)
    elif markOperation.isalpha():
        applyStyle(letteredListStyle,story)
    scribus.selectObject(story)

#####################
def decodeInput(res):
    '''Parse user input.'''
    global num,separator
    f = res[0]
    # Test numbered list
    if f.isdigit():
        n = re.search('\d{1,3}',res).group()
        num = int(n)
        # Get separator
        s = re.search('[^\d]{1,1}',res)
        if s:
            separator = s.group()
        return '1'
    # Test alphabetic list
    elif f.isalpha():
        if len(res) > 1: # if there is second letter or separator
            f2 = re.search('[a-zA-Z]{1,2}',res).group()
            if len(f2) > 1:
                digit1 = f2[1].lower()
                num = alphabet.find(digit1)+1
                digit2 = f2[0].lower()
                num2 = alphabet.find(digit2)+1
                num2 = num2 * len(alphabet)
                num = num + num2
            else:
                digit1 = f2[0].lower()
                num = alphabet.find(digit1)+1
        else:
            digit1 = f.lower()
            num = alphabet.find(digit1)+1
        # Get separator
        s = re.search('[^a-zA-Z]{1,1}',res)
        if s:
            separator = s.group()
    elif f == bulletOperation: pass
    elif f == cleanOperation: pass
    else: f = ''
    return f
    
#####################
def handleSelected():
    """Handle frame selection."""
    global markOperation,separator
    story = scribus.getSelectedObject(0) 
    if story:
        res = scribus.valueDialog(scriptWindowTitle,askOperationMsg,defaultOperation)
        res = res.strip()
        if res:
            markOperation = decodeInput(res)
            if markOperation:
                doAllOperation(story)
                scribus.docChanged(True)
    else:
        scribus.messageBox(scriptWindowTitle,askSelectText,ICON_WARNING,BUTTON_OK)

############### 
def main(argv):
    """Main entry point."""
    if scribus.haveDoc():
        scribus.setRedraw(False)
        handleSelected()
    else:  
        scribus.messageBox(scriptWindowTitle,askOpenDoc,ICON_WARNING,BUTTON_OK)

#######################
def main_wrapper(argv):
    try:
        scribus.statusMessage("Running script...")
        scribus.progressReset()
        main(argv)
    finally:
        if scribus.haveDoc():
            scribus.setRedraw(True)
        scribus.statusMessage("")
        scribus.progressReset()

if __name__ == '__main__':
    main_wrapper(sys.argv)