This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
tabellioOOo/addon/Tabellio.py

2673 lines
101 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: UTF-8 -*-
# TabellioOOo - OpenOffice.org extension
# Copyright (C) 2007-2010 Parlement de la Communauté française de Belgique
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import csv
import string
import sys
import tempfile
import xml.dom.minidom
import socket
import random
import time
if hasattr(socket, 'setdefaulttimeout'):
socket.setdefaulttimeout(30)
import urllib.request, urllib.error, urllib.parse
import urllib.parse
import uno
import unohelper
from com.sun.star.container import NoSuchElementException
from com.sun.star.task import XJobExecutor
from com.sun.star.awt import XActionListener, XItemListener
from com.sun.star.frame import XControlNotificationListener
from com.sun.star.frame import XDispatch
from com.sun.star.frame import XDispatchProvider
from com.sun.star.frame import FeatureStateEvent
from com.sun.star.beans import PropertyValue, NamedValue
from com.sun.star.frame import ControlCommand
from com.sun.star.task import XJob
from com.sun.star.awt import WindowDescriptor
from com.sun.star.awt import FontDescriptor
from com.sun.star.awt.FontWeight import BOLD, NORMAL
from com.sun.star.awt.WindowClass import MODALTOP
from com.sun.star.awt.VclWindowPeerAttribute import OK
from com.sun.star.text.ControlCharacter import APPEND_PARAGRAPH
from com.sun.star.ui.dialogs.TemplateDescription import FILESAVE_SIMPLE
### Utility functions
def debug_print(*args):
'''Print a message on the console (if possible)'''
if sys.platform.startswith('win'):
# there is no stdout/stderr on windows
return
if type(args) in (str, str):
print(args, file=sys.stderr)
else:
for a in args:
print(a, end=' ', file=sys.stderr)
print('', file=sys.stderr)
def debug_unodir(unoobj):
'''Introspect an object to get all its methods and properties.'''
from com.sun.star.beans.MethodConcept import ALL as ALLMETHS
from com.sun.star.beans.PropertyConcept import ALL as ALLPROPS
ctx = uno.getComponentContext()
introspection = ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.beans.Introspection', ctx)
access = introspection.inspect(unoobj)
return {'methods': [x.getName() for x in access.getMethods(ALLMETHS)],
'properties': [x.getName() for x in access.getMethods(ALLPROPS)]}
xlate = {
'\N{ACUTE ACCENT}': "'",
'\N{BROKEN BAR}': '|',
'\N{DIVISION SIGN}': '/',
'\N{LATIN CAPITAL LETTER A WITH ACUTE}': 'A',
'\N{LATIN CAPITAL LETTER A WITH CIRCUMFLEX}': 'A',
'\N{LATIN CAPITAL LETTER A WITH DIAERESIS}': 'A',
'\N{LATIN CAPITAL LETTER A WITH GRAVE}': 'A',
'\N{LATIN CAPITAL LETTER A WITH RING ABOVE}': 'A',
'\N{LATIN CAPITAL LETTER A WITH TILDE}': 'A',
'\N{LATIN CAPITAL LETTER AE}': 'Ae',
'\N{LATIN CAPITAL LETTER C WITH CEDILLA}': 'C',
'\N{LATIN CAPITAL LETTER E WITH ACUTE}': 'E',
'\N{LATIN CAPITAL LETTER E WITH CIRCUMFLEX}': 'E',
'\N{LATIN CAPITAL LETTER E WITH DIAERESIS}': 'E',
'\N{LATIN CAPITAL LETTER E WITH GRAVE}': 'E',
'\N{LATIN CAPITAL LETTER ETH}': 'Th',
'\N{LATIN CAPITAL LETTER I WITH ACUTE}': 'I',
'\N{LATIN CAPITAL LETTER I WITH CIRCUMFLEX}': 'I',
'\N{LATIN CAPITAL LETTER I WITH DIAERESIS}': 'I',
'\N{LATIN CAPITAL LETTER I WITH GRAVE}': 'I',
'\N{LATIN CAPITAL LETTER N WITH TILDE}': 'N',
'\N{LATIN CAPITAL LETTER O WITH ACUTE}': 'O',
'\N{LATIN CAPITAL LETTER O WITH CIRCUMFLEX}': 'O',
'\N{LATIN CAPITAL LETTER O WITH DIAERESIS}': 'O',
'\N{LATIN CAPITAL LETTER O WITH GRAVE}': 'O',
'\N{LATIN CAPITAL LETTER O WITH STROKE}': 'O',
'\N{LATIN CAPITAL LETTER O WITH TILDE}': 'O',
'\N{LATIN CAPITAL LETTER THORN}': 'th',
'\N{LATIN CAPITAL LETTER U WITH ACUTE}': 'U',
'\N{LATIN CAPITAL LETTER U WITH CIRCUMFLEX}': 'U',
'\N{LATIN CAPITAL LETTER U WITH DIAERESIS}': 'U',
'\N{LATIN CAPITAL LETTER U WITH GRAVE}': 'U',
'\N{LATIN CAPITAL LETTER Y WITH ACUTE}': 'Y',
'\N{LATIN SMALL LETTER A WITH ACUTE}': 'a',
'\N{LATIN SMALL LETTER A WITH CIRCUMFLEX}': 'a',
'\N{LATIN SMALL LETTER A WITH DIAERESIS}': 'a',
'\N{LATIN SMALL LETTER A WITH GRAVE}': 'a',
'\N{LATIN SMALL LETTER A WITH RING ABOVE}': 'a',
'\N{LATIN SMALL LETTER A WITH TILDE}': 'a',
'\N{LATIN SMALL LETTER AE}': 'ae',
'\N{LATIN SMALL LETTER C WITH CEDILLA}': 'c',
'\N{LATIN SMALL LETTER E WITH ACUTE}': 'e',
'\N{LATIN SMALL LETTER E WITH CIRCUMFLEX}': 'e',
'\N{LATIN SMALL LETTER E WITH DIAERESIS}': 'e',
'\N{LATIN SMALL LETTER E WITH GRAVE}': 'e',
'\N{LATIN SMALL LETTER ETH}': 'th',
'\N{LATIN SMALL LETTER I WITH ACUTE}': 'i',
'\N{LATIN SMALL LETTER I WITH CIRCUMFLEX}': 'i',
'\N{LATIN SMALL LETTER I WITH DIAERESIS}': 'i',
'\N{LATIN SMALL LETTER I WITH GRAVE}': 'i',
'\N{LATIN SMALL LETTER N WITH TILDE}': 'n',
'\N{LATIN SMALL LETTER O WITH ACUTE}': 'o',
'\N{LATIN SMALL LETTER O WITH CIRCUMFLEX}': 'o',
'\N{LATIN SMALL LETTER O WITH DIAERESIS}': 'o',
'\N{LATIN SMALL LETTER O WITH GRAVE}': 'o',
'\N{LATIN SMALL LETTER O WITH STROKE}': 'o',
'\N{LATIN SMALL LETTER O WITH TILDE}': 'o',
'\N{LATIN SMALL LETTER SHARP S}': 'ss',
'\N{LATIN SMALL LETTER THORN}': 'th',
'\N{LATIN SMALL LETTER U WITH ACUTE}': 'u',
'\N{LATIN SMALL LETTER U WITH CIRCUMFLEX}': 'u',
'\N{LATIN SMALL LETTER U WITH DIAERESIS}': 'u',
'\N{LATIN SMALL LETTER U WITH GRAVE}': 'u',
'\N{LATIN SMALL LETTER Y WITH ACUTE}': 'y',
'\N{LATIN SMALL LETTER Y WITH DIAERESIS}': 'y',
}
def latin1_to_ascii(unicrap):
"""This takes a UNICODE string and replaces Latin-1 characters with
something equivalent in 7-bit ASCII. It returns a plain ASCII string.
This function makes a best effort to convert Latin-1 characters into
ASCII equivalents. It does not just strip out the Latin-1 characters.
All characters in the standard 7-bit ASCII range are preserved.
In the 8th bit range all the Latin-1 accented letters are converted
to unaccented equivalents. Most symbol characters are converted to
something meaningful. Anything not converted is deleted.
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/251871>
"""
r = ""
for i in unicrap:
if i in xlate:
r += xlate[i]
elif ord(i) >= 0x80:
pass
else:
r += str(i)
return r
def normalize_filename(filename):
'''Normalize a filename received from OOo (as a RFC 1738 URL) so it can
get accessed by Python file functions afterwards'''
filename = str(urllib.parse.unquote(str(filename[7:])), 'utf-8')
if sys.platform.startswith('win'):
# Note on behaviour on Windows:
# when on a lettered disk, it returns file:///Z:/foobar
# on a share, it returns file://name-of-the-share/boobar
if filename[0] != '/':
# windows share
filename = '\\\\' + filename
elif filename[2] == ':':
filename = filename[1:]
filename = filename.replace('/', '\\')
return filename
class DownloadError(Exception):
pass
def display_exception(ctx=None):
'''Display exception in a message dialog'''
# it allows to trace errors that would otherwise be swallowed by
# OpenOffice.org, and to get them displayed to terminal for easier
# handling, on non-Windows platforms.
import traceback
if not sys.platform.startswith('win'):
# also print trace on stdout/stderr on non-Windows platform
traceback.print_exc()
if not ctx:
return
s = '\n'.join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
smgr = ctx.ServiceManager
desktop = smgr.createInstanceWithContext('com.sun.star.frame.Desktop',ctx )
doc = desktop.getCurrentComponent()
parentwin = doc.CurrentController.Frame.ContainerWindow
MessageBox(parentwin, s, 'Exception')
# from danny/OOoLib:
def makePropertyValue( cName=None, uValue=None, nHandle=None, nState=None ):
"""Create a com.sun.star.beans.PropertyValue struct and return it.
"""
oPropertyValue = PropertyValue()
if cName != None:
oPropertyValue.Name = cName
if uValue != None:
oPropertyValue.Value = uValue
if nHandle != None:
oPropertyValue.Handle = nHandle
if nState != None:
oPropertyValue.State = nState
return oPropertyValue
def makeNamedList(items):
p = NamedValue()
p.Name = 'List'
p.Value = uno.Any('[]string', items)
return (p,)
def get_url_opener(ctx):
'''Return a urllib2 opener object, configured with appropriate proxy
settings'''
oConfigAccess = getConfigAccess(ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
try:
proxy_server_url = oConfigAccess.getByName('ProxyServerURL').strip()
except: # com.sun.star.container.NoSuchElementException
# this happens in some case of extension misconfiguration, ignore
proxy_server_url = ''
if proxy_server_url in ('system', ''):
return urllib.request.build_opener()
if proxy_server_url == 'none':
proxy_handler = urllib.request.ProxyHandler({})
else:
proxy_handler = urllib.request.ProxyHandler({'http': proxy_server_url})
return urllib.request.build_opener(proxy_handler)
def download(ctx, href):
'''Download and cache files'''
if sys.platform.startswith('win'):
download_cache_dir = 'c:\\temp'
filename = urllib.parse.urlparse(href)[2].strip('/')
else:
download_cache_dir = os.path.join(tempfile.gettempdir(), 'tabellio-cache')
filename = '/'.join(urllib.parse.urlparse(href)[1:3])
filename = filename.replace('/', os.path.sep)
cache_filename = os.path.join(download_cache_dir, filename)
cache_dir = os.path.split(cache_filename)[0]
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
if not os.path.exists(cache_filename) or time.time() - os.stat(cache_filename).st_mtime > 7200:
try:
s = get_url_opener(ctx).open(href).read()
except (urllib.error.HTTPError, urllib.error.URLError, socket.timeout) as e:
if os.path.exists(cache_filename):
return cache_filename
return None
fd = open(cache_filename, 'wb')
fd.write(s)
fd.close()
return cache_filename
def get_text_node_content(node):
'''Return the content of a text node'''
rc = ''
for n in node.childNodes:
if n.nodeType == n.TEXT_NODE:
rc = rc + n.data
return rc
def get_mode(ctx):
oConfigAccess = getConfigAccess(ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
return oConfigAccess.getByName('Mode')
class RemoteObject:
'''
Class to handle objects described in a remote XML file, so they can be
inserted in the document.
'''
_attrs = []
aspres = 0
def __init__(self, node):
for attr in self._attrs:
try:
setattr(self, attr, get_text_node_content(
node.getElementsByTagName(attr)[0]).strip())
except IndexError:
setattr(self, attr, None)
def get_name(self):
return '%s %s' % (self.name, self.firstname)
def get_download_url(cls, ctx):
oConfigAccess = getConfigAccess(ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
inserts_base_url = oConfigAccess.getByName('InsertsRootURL')
download_url = inserts_base_url + cls._download_file
return download_url
get_download_url = classmethod(get_download_url)
def get_nodes(cls, ctx):
list_filename = download(ctx, cls.get_download_url(ctx))
if not list_filename:
raise DownloadError()
dom = xml.dom.minidom.parseString(open(list_filename).read())
nodes = dom.childNodes[0].getElementsByTagName(cls._node_name)
return nodes
get_nodes = classmethod(get_nodes)
_cache = None
def values(cls, ctx):
if cls._cache:
return cls._cache
cls._cache = [cls(x) for x in cls.get_nodes(ctx)]
return cls._cache
values = classmethod(values)
def insert(self, ctx, doc, cursor):
cursor.Text.insertString(cursor, self.get_name().replace(' ', ' '), 0)
def insert_speaker_closing(self, ctx, doc, cursor):
if get_mode(ctx) == 'PFB':
doc.Text.insertString(cursor, '.- ', 0)
else:
doc.Text.insertString(cursor, '.  ', 0)
class Deputy(RemoteObject):
'''
Class to handle description of a deputy
'''
_attrs = ('id', 'firstname', 'name', 'title', 'comppol', 'classname', 'sexe')
_download_file = 'Parls.xml'
_node_name = 'SParlSpeaker'
def get_long_name(self):
return '%s %s %s (%s)' % (self.title, self.firstname, self.name, self.comppol)
def values(cls, ctx):
if cls._cache:
return cls._cache
cls._cache = [cls(x) for x in cls.get_nodes(ctx) if x.getElementsByTagName('firstname')]
return cls._cache
values = classmethod(values)
def insert_as_speaker(self, ctx, doc, cursor):
# Add an annotation next to the document, with a reference to deputy
# internal id
annotation = doc.createInstance('com.sun.star.text.TextField.Annotation')
annotation.setPropertyValue('Author', '')
text = 'type: TABELLIO\nref id: %s\nclassname: %s' % (self.id, self.classname)
annotation.setPropertyValue('Content', text)
doc.Text.insertTextContent(cursor, annotation, False)
cursor.setPropertyValue('CharWeight', BOLD)
doc.Text.insertString(cursor, self.get_long_name().replace(' ', ' '), 0)
self.insert_speaker_closing(ctx, doc, cursor)
cursor.setPropertyValue('CharWeight', NORMAL)
def get_ascii_name(self):
return latin1_to_ascii('%s %s' % (self.firstname, self.name))
def get_deputy_id(cls, firstname, lastname, ctx=None):
'''
Lookup a deputy while ignoring case and accentuated characters
'''
ascii_name = latin1_to_ascii('%s %s' % (firstname, lastname)).lower()
for deputy in cls.values(ctx=ctx):
if ascii_name == deputy.get_ascii_name().lower():
return deputy.id
return None
get_deputy_id = classmethod(get_deputy_id)
class Minister(RemoteObject):
'''
Class to handle the description of a minister
'''
_attrs = ('id', 'firstname', 'name', 'title', 'classname', 'fonc')
_download_file = 'Ministres.xml'
_node_name = 'SMinistreSpeaker'
def get_long_name(self):
return '%s %s %s' % (self.title, self.firstname, self.name)
def get_function_with_correct_case(self, ctx):
if not self.fonc:
self.fonc = ''
fonc = self.fonc.strip()
fonc = fonc.replace('Ministre', 'ministre')
if get_mode(ctx) == 'PCF':
fonc = fonc.replace('Président', 'président')
fonc = fonc.replace('Vice-président', 'vice-président')
return fonc
def insert_as_speaker(self, ctx, doc, cursor):
# Add an annotation next to the document, with a reference to minister
# internal id
annotation = doc.createInstance('com.sun.star.text.TextField.Annotation')
annotation.setPropertyValue('Author', '')
text = 'type: TABELLIO\nref id: %s\nclassname: %s' % (self.id, self.classname)
annotation.setPropertyValue('Content', text)
doc.Text.insertTextContent(cursor, annotation, False)
cursor.setPropertyValue('CharWeight', BOLD)
doc.Text.insertString(cursor, self.get_long_name().replace(' ', ' '), 0)
fonc = self.get_function_with_correct_case(ctx)
if fonc:
if get_mode(ctx) == 'PCF':
cursor.setPropertyValue('CharWeight', NORMAL)
doc.Text.insertString(cursor, ', ', 0)
doc.Text.insertString(cursor, fonc, 0)
if get_mode(ctx) == 'PFB':
cursor.setPropertyValue('CharWeight', NORMAL)
self.insert_speaker_closing(ctx, doc, cursor)
class President(RemoteObject):
'''
Class to handle the description of the president
'''
_attrs = ('id', 'firstname', 'name', 'title', 'classname', 'function', 'sexe')
_download_file = 'President.xml'
_node_name = 'SParlSpeaker'
aspres = 1
def get_long_name(self):
if self.sexe == 'M':
return 'M. le Président'
else:
return 'Mme la Présidente'
def get_nodes(cls, ctx):
list_filename = download(ctx, cls.get_download_url(ctx))
if not list_filename:
raise DownloadError()
dom = xml.dom.minidom.parseString(open(list_filename).read())
# this document has only one element, and it is not embedded into a
# list
return [dom.childNodes[0]]
get_nodes = classmethod(get_nodes)
def insert_as_speaker(self, ctx, doc, cursor):
# Add an annotation next to the document, with a reference to minister
# internal id
annotation = doc.createInstance('com.sun.star.text.TextField.Annotation')
annotation.setPropertyValue('Author', '')
text = 'type: TABELLIO\nref id: %s\nclassname: %s' % (self.id, self.classname)
annotation.setPropertyValue('Content', text)
doc.Text.insertTextContent(cursor, annotation, False)
cursor.setPropertyValue('CharWeight', BOLD)
doc.Text.insertString(cursor, self.get_long_name().replace(' ', ' '), 0)
self.insert_speaker_closing(ctx, doc, cursor)
cursor.setPropertyValue('CharWeight', NORMAL)
class PresCom(RemoteObject):
'''
Class to handle a president of commission
'''
_attrs = ('id', 'firstname', 'name', 'title', 'classname', 'com_code', 'sexe', 'function')
_download_file = 'PresComs.xml'
_node_name = 'SPresComSpeaker'
classname = 'PresCom'
def get_name(self):
return '%s %s (%s)' % (self.name, self.firstname, self.com_code)
def get_long_name(self, ctx):
if get_mode(ctx) == 'PCF':
if self.sexe == 'M':
function = 'le président'
else:
function = 'la présidente'
return '%s %s' % (self.title, function)
else:
if self.function:
return '%s %s %s, %s' % (self.title, self.firstname, self.name, self.function)
if self.sexe == 'M':
function = 'président'
else:
function = 'présidente'
return '%s %s %s, %s' % (self.title, self.firstname, self.name, function)
def insert_as_speaker(self, ctx, doc, cursor):
# Add an annotation next to the document, with a reference to minister
# internal id
annotation = doc.createInstance('com.sun.star.text.TextField.Annotation')
annotation.setPropertyValue('Author', '')
text = 'type: TABELLIO\nref id: %s\nclassname: %s' % (self.id, self.classname)
annotation.setPropertyValue('Content', text)
doc.Text.insertTextContent(cursor, annotation, False)
cursor.setPropertyValue('CharWeight', BOLD)
doc.Text.insertString(cursor, self.get_long_name(ctx).replace(' ', ' '), 0)
self.insert_speaker_closing(ctx, doc, cursor)
cursor.setPropertyValue('CharWeight', NORMAL)
class GenericPresCom(PresCom):
'''
Class to handle a random president of commission
'''
sex = None
id = 'XXX'
classname = 'GenericPresCom'
def __init__(self, sex):
self.sex = sex
def get_name(self):
if self.sex == 'M':
return 'M. le Président'
else:
return 'Mme la Présidente'
def get_long_name(self, ctx):
return self.get_name()
def insert_as_speaker(self, ctx, doc, cursor):
annotation = doc.createInstance('com.sun.star.text.TextField.Annotation')
annotation.setPropertyValue('Author', '')
text = 'type: TABELLIO\nref id: %s\nclassname: %s' % (self.id, self.classname)
annotation.setPropertyValue('Content', text)
doc.Text.insertTextContent(cursor, annotation, False)
cursor.setPropertyValue('CharWeight', BOLD)
doc.Text.insertString(cursor, self.get_long_name(ctx).replace(' ', ' '), 0)
self.insert_speaker_closing(ctx, doc, cursor)
cursor.setPropertyValue('CharWeight', NORMAL)
class Commission(RemoteObject):
'''
Class to handle a commission
'''
_attrs = ('id', 'nom', 'code', 'classname')
_download_file = 'Commissions.xml'
_node_name = 'MCOMSInfo'
def get_name(self):
if len(self.nom) > 60:
return self.nom[:self.nom[:65].rindex(' ')] + '... (%s)' % self.code
return self.nom
def get_long_name(self):
return self.nom
def insert(self, ctx, cursor):
cursor.Text.insertString(cursor, self.get_long_name(), 0)
def get_min_pres_menu_items(ctx):
values = Minister.values(ctx) + President.values(ctx) + PresCom.values(ctx)
values.append(GenericPresCom('M'))
values.append(GenericPresCom('F'))
return values
class SnippetDoc(RemoteObject):
'''
Class to handle a snippet document
'''
_download_file = 'Docs.xml'
_node_name = 'document'
def __init__(self, node):
self.title = get_text_node_content(node)
self.filename = node.getAttribute('filename')
class CloseListener(unohelper.Base, XActionListener):
'''
Listener to close the dialogs
'''
def __init__(self, dialog):
self.dialog = dialog
def actionPerformed(self, actionEvent):
self.dialog.endExecute()
class InsertSpeakerDlgListener(unohelper.Base, XActionListener):
'''
Listener for the insert speaker dialog box
'''
def __init__(self, ctx, doc, combobox, dialog, values):
self.ctx = ctx
self.combobox = combobox
self.dialog = dialog
self.doc = doc
self.values = values
RemoteObject.ctx = ctx
def actionPerformed(self, actionEvent):
try:
cursor = self.doc.getCurrentController().getViewCursor()
string = self.combobox.Text
t = [x for x in self.values if x.get_name() == string]
if not t:
raise Exception('Unknown')
t[0].insert_as_speaker(self.ctx, self.doc, cursor)
self.dialog.endExecute()
except:
display_exception(self.ctx)
def MessageBox(ParentWin, MsgText, MsgTitle, MsgType="messbox", MsgButtons=OK):
'''Show a message box with the UNO based toolkit'''
MsgType = MsgType.lower()
#available msg types
MsgTypes = ("messbox", "infobox", "errorbox", "warningbox", "querybox")
if not ( MsgType in MsgTypes ):
MsgType = "messbox"
#describe window properties.
aDescriptor = WindowDescriptor()
aDescriptor.Type = MODALTOP
aDescriptor.WindowServiceName = MsgType
aDescriptor.ParentIndex = -1
aDescriptor.Parent = ParentWin
aDescriptor.WindowAttributes = MsgButtons
tk = ParentWin.getToolkit()
msgbox = tk.createWindow(aDescriptor)
msgbox.MessageText = MsgText
if MsgTitle :
msgbox.CaptionText = MsgTitle
return msgbox.execute()
class StyleApply(unohelper.Base, XJobExecutor):
'''
Job to apply a style to the current paragraph; it is used in styles and
legistic styles toolbars.
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
style_name = args
desktop = self.ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.frame.Desktop", self.ctx )
doc = desktop.getCurrentComponent()
cursor = doc.getCurrentController().getViewCursor()
try:
text = doc.Text
textcursor = text.createTextCursorByRange(cursor.getStart())
numberingrules = textcursor.getPropertyValue('NumberingRules')
if numberingrules:
textcursor.setPropertyValue('NumberingRules', None)
cursor.setPropertyValue('ParaStyleName', style_name)
if style_name == 'TitreSynthese':
cursor.Text.insertString(cursor, 'Résumé', 0)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
cursor.setPropertyValue('ParaStyleName', 'Text body')
dispatchHelper = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.DispatchHelper', self.ctx)
cursor = text.createTextCursor()
view_cursor = doc.getCurrentController().getViewCursor()
cursor.gotoRange(view_cursor, False)
frame = doc.getCurrentController().getFrame()
dispatchHelper.executeDispatch(frame, '.uno:GoToStartOfLine', '', 0, ())
dispatchHelper.executeDispatch(frame, '.uno:EndOfParaSel', '', 0, ())
dispatchHelper.executeDispatch(frame, '.uno:ResetAttributes', '', 0, ())
view_cursor.gotoRange(cursor, False)
except:
display_exception(self.ctx)
class ListStyleApply(unohelper.Base, XJobExecutor):
'''
Job to apply a list style.
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
'''
Style current paragraph as list, argument should be one of ARABIC,
CHARS_UPPER_LETTER, CHARS_LOWER_LETTER, ROMAN_UPPER, ROMAN_LOWER,
or DASH.
'''
log = []
try:
style = args
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
text = doc.Text
cursor = doc.getCurrentController().getViewCursor()
# look at the previous paragraph, if it's already a list item, then
# it's better not to create a new list, but adding to the existing
# list; this makes it possible to repeatedly click on the "listize"
# toolbar button over consecuting paragraphs
previouspara_textcursor = text.createTextCursorByRange(cursor.getStart())
previouspara_textcursor.gotoPreviousParagraph(False)
numberingrules = None
if previouspara_textcursor.ParaStyleName == 'Text body':
level = previouspara_textcursor.getPropertyValue('NumberingLevel')
numberingrules = previouspara_textcursor.getPropertyValue('NumberingRules')
if numberingrules:
props = numberingrules.getByIndex(level)
for prop in props:
if prop.Name == 'NumberingType' and (
(prop.Value == 0 and style == 'CHARS_UPPER_LETTER') or
(prop.Value == 1 and style == 'CHARS_LOWER_LETTER') or
(prop.Value == 2 and style == 'ROMAN_UPPER') or
(prop.Value == 3 and style == 'ROMAN_LOWER') or
(prop.Value == 4 and style == 'ARABIC')):
# found a similar numbering rule in the
# previous paragraph, keep on using it
break
else:
numberingrules = None
# Back to current paragraph
textcursor = text.createTextCursorByRange(cursor.getStart())
level = textcursor.getPropertyValue('NumberingLevel')
if numberingrules is None:
numberingrules = textcursor.getPropertyValue('NumberingRules')
if numberingrules is None:
log.append('using new numbering rules array')
numberingrules = doc.createInstance('com.sun.star.text.NumberingRules')
else:
log.append('using numbering rules of current paragraph')
else:
log.append('using numbering rules of previous paragraph')
props = numberingrules.getByIndex(level)
found_bullet_char_prop = False
found_bullet_fontname_prop = False
log.append('style level %s as %s' % (level, style))
for i, prop in enumerate(props):
log.append(' initial prop: %s: %r' % (prop.Name, prop.Value))
if prop.Name == 'CharStyleName':
if style in ('DASH', 'BULLET'):
prop.Value = 'Bullet Symbols'
else:
prop.Value = 'Numbering Symbols'
log.append(' -> changed to %r' % prop.Value)
elif prop.Name == 'BulletChar' and style in ('DASH', 'BULLET'):
found_bullet_char_prop = True
if style == 'DASH':
prop.Value = '-'
else:
prop.Value = ''
log.append(' -> changed to %r' % prop.Value)
elif prop.Name == 'NumberingType':
if style == 'CHARS_UPPER_LETTER':
prop.Value = 0
elif style == 'CHARS_LOWER_LETTER':
prop.Value = 1
elif style == 'ROMAN_UPPER':
prop.Value = 2
elif style == 'ROMAN_LOWER':
prop.Value = 3
elif style == 'ARABIC':
prop.Value = 4
else:
prop.Value = 6
log.append(' -> changed to %r' % prop.Value)
elif prop.Name == 'BulletFontName':
if style in ('DASH', 'BULLET'):
prop.Value = ''
log.append(' -> changed to %r' % prop.Value)
if style in ('DASH', 'BULLET'):
props = list(props)
if not found_bullet_char_prop:
if style == 'DASH':
props.append(makePropertyValue('BulletChar', '-'))
else:
props.append(makePropertyValue('BulletChar', ''))
log.append(' -> add BulletChar as %r' % props[-1].Value)
if not found_bullet_fontname_prop:
log.append(' -> add BulletFontName')
props.append(makePropertyValue('BulletFontName', ''))
props = tuple(props)
uno.invoke(numberingrules, 'replaceByIndex',
(level, uno.Any("[]com.sun.star.beans.PropertyValue", props)) )
textcursor.setPropertyValue('NumberingRules', numberingrules)
except Exception as e:
display_exception(self.ctx)
log.append('\nException data:')
log.append('%s' % type(e))
log.append('%r' % e.__dict__)
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
parentwin = doc.CurrentController.Frame.ContainerWindow
MessageBox(parentwin, '\n'.join(log), 'Debug')
class StructureError:
'''
Class describing an error in the structure of the document
'''
type = None
paragraph_index = 0
page_no = None
para_style = None
def __init__(self, type, *args):
self.type = type
if type == 'skipped some level':
self.lastStyle = args[0].replace('Heading', 'Titre')
self.currentStyle = args[1].replace('Heading', 'Titre')
def get_short(self):
'''Get a short summary of the error'''
if self.type == 'same level without content':
return 'Deux titres consécutifs sans contenu intermédiaire'
if self.type == 'skipped some level':
return 'Saut de titre (niveau %s à %s)' % (self.lastStyle, self.currentStyle)
if self.type == 'went below top level':
return 'Niveau mal placé (trop bas)'
if self.type == 'started legistic section too low':
return 'Les parties légistiques doivent démarrer à un niveau supérieur'
if self.type == 'preface in wrong place':
return 'La préface est placée à un mauvais endroit'
if self.type == 'two prefaces':
return 'Le document contient deux préfaces'
if self.type == 'word-copy-paste-horizontal-line':
return 'Ligne horizontale, vraisemblablement copié/collé'
if self.type == 'paragraph-filled-with-nothing-but-spaces':
return 'Paragraphe composé uniquement d\'espaces'
def get_long(self):
'''Get a longer description of the error'''
if self.type == 'same level without content':
return '''Deux titres de même niveau se suivent et il n'y a aucun contenu entre les deux'''
if self.type == 'skipped some level':
return '''Hiérarchie du document non respectée. Passage sans intermédiaire du niveau %s au niveau %s.''' % (self.lastStyle, self.currentStyle)
if self.type == 'went below top level':
return 'Niveau mal placé (trop bas)'
if self.type == 'started legistic section too low':
return '''Erreur de hiérarchie légistique. Une partie légistique ne peut démarrer au niveau section ou sous-section. Les choix possibles sont Partie, Livre, Titre ou chapitre.'''
if self.type == 'preface in wrong place':
return '''La préface doit se trouver à la base du document ou au niveau "Partie".'''
if self.type == 'two prefaces':
return 'Une seule préface est autorisée dans le document.'
if self.type == 'word-copy-paste-horizontal-line':
return '''Une ligne horizontale provient généralement d'un '''\
'''copié/collé depuis Microsoft Word d'un texte '''\
'''contenant une note de bas de page.'''
if self.type == 'paragraph-filled-with-nothing-but-spaces':
return 'Paragraphe composé uniquement d\'espaces'
class StructureCheckListListener(unohelper.Base, XItemListener):
'''
Listener for the structure check dialog
'''
def __init__(self, dialog, doc, errors):
self.dialog = dialog
self.doc = doc
self.errors = errors
def itemStateChanged(self, event):
try:
label = self.dialog.getControl('detail')
error = self.errors[event.Selected]
label.setText(error.get_long())
text = self.doc.Text
cursor = text.createTextCursor()
cursor.gotoStart(False)
for i in range(error.paragraph_index):
cursor.gotoNextParagraph(False)
view_cursor = self.doc.getCurrentController().getViewCursor()
view_cursor.gotoRange(cursor, False)
except:
display_exception()
class StructureCheckDialog(unohelper.Base, XActionListener):
'''
Dialog to display the result ot a structure check
'''
def __init__(self, ctx, doc, errors, continue_action = None, continue_label = 'Continuer'):
self.ctx = ctx
self.continue_action = continue_action
self.continue_label = continue_label
self.doc = doc
self.errors = errors
def show(self):
smgr = self.ctx.ServiceManager
# create the dialog model and set the properties
dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', self.ctx)
dialogModel.PositionX = 100
dialogModel.PositionY = 200
dialogModel.Width = 210
dialogModel.Height = 130
dialogModel.Title = 'Analyse du document'
listModel = addWidget(dialogModel, 'errorList', 'ListBox', 5, 5, 200, 70)
listErrors = []
for i, error in enumerate(self.errors):
if error.type == 'word-copy-paste-horizontal-line':
listErrors.append('Page %s: %s' % (error.page_no, error.get_short()))
else:
listErrors.append('Page %s: %s: %s' % (error.page_no,
error.para_style.replace('Heading', 'Titre'), error.get_short()))
listModel.StringItemList = tuple(listErrors)
text = addWidget(dialogModel, 'detail', 'FixedText', 5, 80, 200, 22)
text.MultiLine = True
text.Label = ''
if self.continue_action:
# continue button
button = addWidget(dialogModel, 'continueButton', 'Button', 155, 110, 50, 14)
button.TabIndex = 0
button.Label = 'Continuer'
button = addWidget(dialogModel, 'closeButton', 'Button', 95, 110, 50, 14)
button.TabIndex = 1
button.Label = 'Annuler'
else:
button = addWidget(dialogModel, 'closeButton', 'Button', 155, 110, 50, 14)
button.TabIndex = 0
button.Label = 'Fermer'
# create the dialog control and set the model
controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', self.ctx)
controlContainer.setModel(dialogModel)
if self.continue_action:
controlContainer.getControl('continueButton').setActionCommand('continue')
controlContainer.getControl('continueButton').addActionListener(self)
controlContainer.getControl('closeButton').addActionListener(self)
controlContainer.getControl('errorList').addItemListener(
StructureCheckListListener(controlContainer, self.doc, self.errors))
self.dialog = controlContainer
# create a peer
toolkit = smgr.createInstanceWithContext(
'com.sun.star.awt.ExtToolkit', self.ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
controlContainer.setVisible(True)
def actionPerformed(self, actionEvent):
self.dialog.setVisible(False)
self.dialog.dispose()
if actionEvent.ActionCommand == 'continue':
self.continue_action(self.doc)
def check_structure(doc):
'''Check structure of the document, returns a list of errors'''
errors = []
text = doc.Text
view_cursor = doc.getCurrentController().getViewCursor()
cursor = text.createTextCursor()
cursor.gotoStart(False)
lastStyle = None
lastLegisticStyle = None
seenContent = False
seenLegiContent = False
topLevel = 0
topLegisticLevel = 0
styles = ['Partie', 'Chap', 'Sec 1', 'Sec 1.1', 'Sec 1.1.1', 'Sec 1.1.1.1', 'Ss-Titre']
legistic_styles = ['Lpart', 'LLivre', 'Ltitre', 'Lchapitre', 'Lsection', 'Lsoussection']
paragraph_index = 0
seen_preface = False
while True:
error = None
currentStyle = cursor.ParaStyleName
if currentStyle in styles:
if lastStyle:
last_level = styles.index(lastStyle)
new_level = styles.index(currentStyle)
if new_level == last_level:
if not seenContent:
debug_print('lastStyle:', lastStyle)
debug_print('currentStyle:', currentStyle)
# 'same level without content' check has been disabled
# http://bugzilla.entrouvert.org/show_bug.cgi?id=56
#error = StructureError('same level without content')
seenContent = False
elif new_level == last_level+1:
# ok
seenContent = False
elif new_level > last_level+1:
debug_print('skipped some level', lastStyle, currentStyle)
error = StructureError('skipped some level', lastStyle, currentStyle)
seenContent = False
elif new_level < topLevel:
error = StructureError('went below top level')
seenContent = False
lastStyle = currentStyle
lastLegisticStyle = None
topLegisticLevel = None
elif currentStyle in legistic_styles:
if lastLegisticStyle:
last_legistic_level = legistic_styles.index(lastLegisticStyle)
new_legistic_level = legistic_styles.index(currentStyle)
if new_legistic_level == last_legistic_level:
if not seenLegiContent:
error = StructureError('same level without content')
seenLegiContent = False
elif new_legistic_level == last_legistic_level+1:
# ok
seenLegiContent = False
elif new_legistic_level > last_legistic_level+1:
debug_print('skipped some level', lastStyle, currentStyle)
error = StructureError('skipped some level', lastStyle, currentStyle)
seenLegiContent = False
elif new_legistic_level < topLegisticLevel:
error = StructureError('went below top level')
seenLegiContent = False
else:
# first time in a legistic section
new_legistic_level = legistic_styles.index(currentStyle)
if new_legistic_level > legistic_styles.index('Lchapitre'):
error = StructureError('started legistic section too low')
topLegisticLevel = new_legistic_level
lastLegisticStyle = currentStyle
elif currentStyle == 'TitrePreface':
# preface can only happen <book> or <part>
if lastStyle:
last_level = styles.index(lastStyle)
if last_level > 0:
error = StructureError('preface in wrong place')
if seen_preface:
error = StructureError('two prefaces')
seen_preface = True
if currentStyle == 'Horizontal Line':
# this is added when copy/pasting footnotes from Microsoft Word
error = StructureError('word-copy-paste-horizontal-line')
else:
if currentStyle not in ('Standard', 'Larttitre'):
debug_print ('unknown style:', currentStyle)
seenContent = True
seenLegiContent = True
cursor.gotoEndOfParagraph(True)
s = cursor.String.replace(' ', '').replace('\t', '').replace('\xa0', '')
if cursor.String and s == '': # paragraph filled with nothing but spaces
#error = StructureError('paragraph-filled-with-nothing-but-spaces')
# do not mark this as an error, they will be ignored automatically
# by odf2legi
pass
if error:
view_cursor.gotoRange(cursor, False)
error.paragraph_index = paragraph_index
error.page_no = view_cursor.getPage()
error.para_style = cursor.ParaStyleName
errors.append(error)
paragraph_index += 1
if not cursor.gotoNextParagraph(False):
break
return errors
class StructureCheck(unohelper.Base, XJobExecutor):
'''
Job to perform a structure check
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.frame.Desktop", self.ctx )
self.doc = desktop.getCurrentComponent()
self.errors = check_structure(self.doc)
if self.errors:
text = self.doc.Text
cursor = text.createTextCursor()
view_cursor = self.doc.getCurrentController().getViewCursor()
cursor.gotoStart(False)
for i in range(self.errors[0].paragraph_index):
cursor.gotoNextParagraph(False)
view_cursor.gotoRange(cursor, False)
dialog = StructureCheckDialog(self.ctx, self.doc, self.errors)
dialog.show()
else:
parentwin = self.doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin,
'Vérification de la structure terminée',
'Tabellio', 'infobox')
except:
display_exception(self.ctx)
class SpeakerDialog(unohelper.Base, XJobExecutor):
'''
Job to display an insert speaker dialog
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
'''
Arg should be one of deputes, ministres, new, commissions
'''
ctx = self.ctx
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
if not args in ('deputes', 'ministres', 'new', 'commissions'):
parentwin = doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin, 'Liste inconnue', 'Alerte', "infobox")
try:
if args not in ('deputes', 'ministres'):
parentwin = doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin, 'Liste inconnue', 'Alerte', "infobox")
smgr = ctx.ServiceManager
# create the dialog model and set the properties
dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', ctx)
dialogModel.PositionX = 100
dialogModel.PositionY = 200
dialogModel.Width = 155
dialogModel.Height = 50
if args == 'ministres':
dialogModel.Title = "Insertion Ministres et Présidents"
elif args == 'deputes':
dialogModel.Title = "Insertion Députés"
# create the button model and set the properties
button = addWidget(dialogModel, 'insertButton', 'Button', 50, 30, 50, 14)
button.TabIndex = 0
button.Label = "Insérer"
text = addWidget(dialogModel, 'label0', 'FixedText', 10, 10, 100, 14)
text.Label = 'Orateur :'
combobox = addWidget(dialogModel, 'insertWidget', 'ComboBox', 50, 7, 100, 14)
combobox.Dropdown = True
if args == 'deputes':
values = Deputy.values(ctx)
elif args == 'ministres':
values = get_min_pres_menu_items(ctx)
combobox.StringItemList = tuple([x.get_name() for x in values])
# create the dialog control and set the model
controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', ctx)
controlContainer.setModel(dialogModel)
# add the action listener
controlContainer.getControl('insertButton').addActionListener(
InsertSpeakerDlgListener(self.ctx, doc, combobox, controlContainer, values))
toolkit = smgr.createInstanceWithContext('com.sun.star.awt.ExtToolkit', ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
controlContainer.execute()
controlContainer.dispose()
except:
display_exception(self.ctx)
class InsertManualSpeakerDlgListener(unohelper.Base, XActionListener):
'''
Listener for the manual speaker insert dialog
'''
def __init__(self, ctx, doc, entry, dialog):
self.ctx = ctx
self.entry = entry
self.doc = doc
self.dialog = dialog
def actionPerformed(self, actionEvent):
try:
cursor = self.doc.getCurrentController().getViewCursor()
string = self.entry.Text
annotation = self.doc.createInstance('com.sun.star.text.TextField.Annotation')
annotation.setPropertyValue('Author', '')
text = 'type: TABELLIO\nclassname: SPEAKER'
annotation.setPropertyValue('Content', text)
self.doc.Text.insertTextContent(cursor, annotation, False)
cursor.setPropertyValue('CharWeight', BOLD)
self.doc.Text.insertString(cursor, string.replace(' ', ' '), 0)
self.doc.Text.insertString(cursor, '.  ', 0)
cursor.setPropertyValue('CharWeight', NORMAL)
self.dialog.endExecute()
except:
display_exception(self.ctx)
class ManualSpeakerDialog(unohelper.Base, XJobExecutor):
'''
Dialog for manual speaker insert
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
ctx = self.ctx
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
try:
smgr = ctx.ServiceManager
# create the dialog model and set the properties
dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', ctx)
dialogModel.PositionX = 100
dialogModel.PositionY = 200
dialogModel.Width = 155
dialogModel.Height = 50
dialogModel.Title = 'Insertion d\'un orateur manuel'
# create the button model and set the properties
button = addWidget(dialogModel, 'insertButton', 'Button', 50, 30, 50, 14)
button.TabIndex = 0
button.Label = "Insérer"
text = addWidget(dialogModel, 'label0', 'FixedText', 10, 10, 100, 14)
text.Label = 'Orateur :'
entry = addWidget(dialogModel, 'insertWidget', 'Edit', 50, 7, 100, 14)
entry.MultiLine = False
# create the dialog control and set the model
controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', ctx)
controlContainer.setModel(dialogModel)
# add the action listener
controlContainer.getControl('insertButton').addActionListener(
InsertManualSpeakerDlgListener(self.ctx, doc, entry, controlContainer))
toolkit = smgr.createInstanceWithContext('com.sun.star.awt.ExtToolkit', ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
controlContainer.execute()
controlContainer.dispose()
except:
display_exception(self.ctx)
def getConfigAccess(ctx, cNodePath, bWriteAccess=False):
oConfigProvider = ctx.ServiceManager.createInstance(
'com.sun.star.configuration.ConfigurationProvider')
if bWriteAccess:
cServiceName = 'com.sun.star.configuration.ConfigurationUpdateAccess'
else:
cServiceName = 'com.sun.star.configuration.ConfigurationAccess'
oConfigAccess = oConfigProvider.createInstanceWithArguments( cServiceName,
( makePropertyValue( 'nodepath', cNodePath ),))
return oConfigAccess
def set_toolbar_visibility(ctx, visibility):
oConfigAccess = getConfigAccess(ctx, '/org.openoffice.Office.Addons/AddonUI')
oElement = oConfigAccess.getByName('OfficeToolBar')
toolbar_names = oElement.getElementNames()
desktop = ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', ctx)
doc = desktop.getCurrentComponent()
if not doc:
components = desktop.getComponents().createEnumeration()
while components.hasMoreElements():
try:
doc = components.nextElement()
except NoSuchElementException:
break
break
layout_manager = doc.CurrentController.Frame.LayoutManager
if visibility is False:
for toolbar in toolbar_names:
layout_manager.hideElement('private:resource/toolbar/addon_%s' % toolbar)
else:
for toolbar in toolbar_names:
layout_manager.showElement('private:resource/toolbar/addon_%s' % toolbar)
try:
President.get_nodes(ctx)
except DownloadError as e:
parentwin = doc.CurrentController.Frame.ContainerWindow
MessageBox(parentwin,
'Les listes d\'orateurs n\'ont pu être chargées (problème de réseau?)',
'Tabellio', 'errorbox')
class ShowHideToolbars(unohelper.Base, XJobExecutor):
'''
Job to display or hide toolbars
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
ctx = self.ctx
try:
set_toolbar_visibility(ctx, args != 'hide')
except:
display_exception(self.ctx)
class AboutDialog(unohelper.Base, XJobExecutor):
'''
Job to display an about Tabellio dialog
'''
def __init__(self, ctx):
self.ctx = ctx
def getLogoUrl(self):
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.FileLocations', False)
exp = self.ctx.getValueByName('/singletons/com.sun.star.util.theMacroExpander')
path = oConfigAccess.getByName('ImageLogo')
url = exp.expandMacros(path)
return url
def trigger(self, args):
ctx = self.ctx
try:
smgr = self.ctx.ServiceManager
dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', self.ctx)
dialogModel.Width = 210
dialogModel.Height = 100
dialogModel.Title = 'À propos de Tabellio'
logo = addWidget(dialogModel, 'logo', 'ImageControl', 5, 5, 155/2, 155/2)
logo.ImageURL = self.getLogoUrl()
text = addWidget(dialogModel, 'title', 'FixedText', 5 + 155/2 + 5, 5, 100, 25)
text.Label = 'Tabellio³'
fdesc = FontDescriptor()
fdesc.Weight = 200
fdesc.Height = 16
text.FontDescriptor = fdesc
# close button
button = addWidget(dialogModel, 'closeButton', 'Button', 155, 80, 50, 14)
button.TabIndex = 0
button.Label = 'Fermer'
# create the dialog control and set the model
controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', self.ctx)
controlContainer.setModel(dialogModel)
controlContainer.getControl('closeButton').addActionListener(
CloseListener(controlContainer))
# create a peer
toolkit = smgr.createInstanceWithContext(
'com.sun.star.awt.ExtToolkit', ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
# execute it
controlContainer.execute()
# dispose the dialog
controlContainer.dispose()
except:
display_exception(self.ctx)
def addWidget(dialog, name, type, x, y, w, h):
'''Utility function to add a widget to a dialog'''
widget = dialog.createInstance('com.sun.star.awt.UnoControl%sModel' % type)
widget.Name = name
widget.PositionX = x
widget.PositionY = y
widget.Width = w
widget.Height = h
dialog.insertByName(name, widget)
return widget
class ConfigurationDlgListener(unohelper.Base, XActionListener):
'''
Listener for the configuration dialog
'''
def __init__(self, ui):
self.ui = ui
def actionPerformed(self, actionEvent):
try:
oConfigAccess = getConfigAccess(self.ui.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', True)
oConfigAccess.PreviewServerURL = self.ui.preview_server_url.Text
oConfigAccess.InsertsRootURL = self.ui.inserts_root_url.Text
oConfigAccess.ProxyServerURL = self.ui.proxy_server_url.Text
oConfigAccess.Mode = self.ui.mode.Text
oConfigAccess.commitChanges()
self.ui.dialog.endExecute()
except:
display_exception(self.ui.ctx)
class ConfigurationDialog(unohelper.Base, XJobExecutor):
'''
Configuration dialog
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
ctx = self.ctx
try:
smgr = self.ctx.ServiceManager
desktop = smgr.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx )
self.doc = doc = desktop.getCurrentComponent()
dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', self.ctx)
dialogModel.Width = 140
dialogModel.Height = 140
dialogModel.Title = 'Configuration'
label = addWidget(dialogModel, 'previewServerUrlLabel', 'FixedText', 5, 5, 100, 10)
label.Label = 'URL du serveur de prévisualisation'
self.preview_server_url = addWidget(dialogModel,
'preview_server_url', 'Edit', 10, 15, 125, 14)
label = addWidget(dialogModel, 'insertsRootUrlLabel', 'FixedText', 5, 35, 100, 10)
label.Label = 'URL racine pour les insertions'
self.inserts_root_url = addWidget(dialogModel,
'inserts_root_url', 'Edit', 10, 45, 125, 14)
label = addWidget(dialogModel, 'proxyServerUrlLabel', 'FixedText', 5, 65, 100, 10)
label.Label = 'URL du proxy'
self.proxy_server_url = addWidget(dialogModel,
'proxy_server_url', 'Edit', 10, 75, 125, 14)
label = addWidget(dialogModel, 'modeLabel', 'FixedText', 5, 95, 100, 10)
label.Label = 'Mode'
self.mode = addWidget(dialogModel,
'mode', 'ComboBox', 10, 105, 125, 14)
self.mode.Dropdown = True
self.mode.StringItemList = (('PCF', 'PFB'))
self.mode.Text = self.mode.StringItemList[0]
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
self.preview_server_url.Text = oConfigAccess.getByName('PreviewServerURL')
self.inserts_root_url.Text = oConfigAccess.getByName('InsertsRootURL')
self.proxy_server_url.Text = oConfigAccess.getByName('ProxyServerURL')
self.mode.Text = oConfigAccess.getByName('Mode')
# save button
saveButton = addWidget(dialogModel, 'saveButton', 'Button', 85, 125, 50, 14)
saveButton.Label = 'Enregistrer'
# cancel button
cancelButton = addWidget(dialogModel, 'cancelButton', 'Button', 30, 125, 50, 14)
cancelButton.Label = 'Annuler'
# create the dialog control and set the model
self.dialog = controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', self.ctx)
controlContainer.setModel(dialogModel)
controlContainer.getControl('cancelButton').addActionListener(
CloseListener(controlContainer))
controlContainer.getControl('saveButton').addActionListener(
ConfigurationDlgListener(self))
# create a peer
toolkit = smgr.createInstanceWithContext(
'com.sun.star.awt.ExtToolkit', ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
# execute it
controlContainer.execute()
# dispose the dialog
controlContainer.dispose()
except:
display_exception(self.ctx)
class InsertVote(unohelper.Base, XJobExecutor):
'''
Job to insert the result of a vote
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
try:
smgr = self.ctx.ServiceManager
desktop = smgr.createInstanceWithContext('com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
filepicker = smgr.createInstance('com.sun.star.ui.dialogs.FilePicker')
filepicker.appendFilter('*.txt', '*.txt')
if filepicker.execute() == 0: # cancel
return
filenames = filepicker.getFiles()
if not filenames:
return
# OOo returns the filenames as RFC 1738 URLs
filename = normalize_filename(filenames[0])
self.insert_vote_result(filename, doc)
except:
display_exception(self.ctx)
def format_list_of_deputes(self, deputes):
'''Format a list of deputies, to have them grouped by gender and
prefixed by the appropriate titles. (e.g. MM. for a group of
mens)'''
deputes.sort(lambda x,y: cmp(x.name.lower(), y.name.lower()))
deputes.reverse()
r = []
if len(deputes) == 2:
# special case, join them with 'et'
depute1 = deputes[0]
depute2 = deputes[1]
if depute1.sexe == depute2.sexe:
if depute1.sexe == 'M':
r.append('MM. ')
else:
r.append('Mmes ')
r.append('%s %s' % (depute1.name, depute1.firstname))
r.append(' et ')
r.append('%s %s' % (depute2.name, depute2.firstname))
else:
if depute1.sexe == 'M':
title = 'M.'
else:
title = 'Mme'
r.append('%s %s %s' % (title, depute1.name, depute1.firstname))
r.append(' et ')
if depute2.sexe == 'M':
title = 'M.'
else:
title = 'Mme'
r.append('%s %s %s' % (title, depute2.name, depute2.firstname))
r.append('.')
return ''.join(r)
while deputes:
depute = deputes.pop()
if r:
r.append(', ')
if len(deputes) == 0 or deputes[-1].sexe != depute.sexe:
if depute.sexe == 'M':
title = 'M.'
else:
title = 'Mme'
r.append('%s %s %s' % (title, depute.name, depute.firstname))
else:
if depute.sexe == 'M':
r.append('MM. ')
else:
r.append('Mmes ')
r.append('%s %s' % (depute.name, depute.firstname))
while deputes and deputes[-1].sexe == depute.sexe:
depute = deputes.pop()
r.append(', %s %s' % (depute.name, depute.firstname))
r.append('.')
return ''.join(r)
def insert_vote_result(self, filename, doc):
'''Insert the result of a vote (taken from a text file) in the current
document.'''
cursor = doc.getCurrentController().getViewCursor()
deputies_dict = {}
for d in Deputy.values(self.ctx):
deputies_dict[d.id] = d
votes_by_deputy = {}
missing_deputies = []
for line_no, vote in enumerate(csv.reader(open(filename), delimiter = '\t')):
parti = vote[3]
lastname = str(vote[4], 'iso-8859-1')
firstname = str(vote[5], 'iso-8859-1')
type = vote[6] # 0, +, - or AB
dep_id = Deputy.get_deputy_id(firstname, lastname, ctx=self.ctx)
if dep_id is None:
# it was not possible to find this person, keep track of this,
# it will be used to display a dialog at the end.
missing_deputies.append((line_no+1, '%s %s' % (firstname, lastname)))
continue
votes_by_deputy[dep_id] = type
# count the different kind of votes
nb_yes = len([x for x in votes_by_deputy if votes_by_deputy.get(x) == '+'])
nb_no = len([x for x in votes_by_deputy if votes_by_deputy.get(x) == '-'])
nb_abst = len([x for x in votes_by_deputy if votes_by_deputy.get(x) == 'AB'])
nb_null = len([x for x in votes_by_deputy if votes_by_deputy.get(x) == '0'])
if nb_yes == 0:
str_yes = 'Aucun membre n\'a répondu oui.'
elif nb_yes == 1:
str_yes = '1 membre a répondu oui.'
else:
str_yes = '%s membres ont répondu oui.' % nb_yes
if nb_no == 0:
str_no = 'Aucun membre n\'a répondu non.'
elif nb_no == 1:
str_no = '1 membre a répondu non.'
else:
str_no = '%s membres ont répondu non.' % nb_no
if nb_abst == 0:
str_abst = 'Aucun membre ne s\'est abstenu.'
elif nb_abst == 1:
str_abst = '1 membre s\'est abstenu.'
else:
str_abst = '%s membres se sont abstenus.' % nb_abst
def insert_string(str):
cursor.Text.insertString(cursor, str, False)
insert_string('%s membres ont pris part au vote.' % sum((nb_yes, nb_no, nb_abst)))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
if nb_no + nb_abst + nb_null == 0:
# -> everyone agree, the text is adopted
insert_string('Tous ont répondu oui.')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string('''En conséquence, [mettre ici l'objet du vote] est adopté. Il sera soumis à la sanction du Gouvernement de la Communauté française. / est adopté et l'article est modifié.''')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy]
insert_string('Ont pris part au vote:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
elif nb_yes > nb_no:
# -> the text is adopted
insert_string(str_yes)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
if nb_no:
insert_string(str_no)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
if nb_abst:
insert_string(str_abst)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string('''En conséquence, [mettre ici l'objet du vote] est adopté. Il sera soumis à la sanction du Gouvernement de la Communauté française. / est adopté et l'article est modifié.''')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy if votes_by_deputy.get(x) == '+']
insert_string('Ont répondu oui:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy if votes_by_deputy.get(x) == '-']
if deputes:
insert_string('Ont répondu non:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy if votes_by_deputy.get(x) == 'AB']
if deputes:
insert_string('Se sont abstenus:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
elif nb_no > nb_yes:
# -> the text is rejected
insert_string(str_no)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
if nb_yes:
insert_string(str_yes)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
if nb_abst:
insert_string(str_abst)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string('''En conséquence, [mettre ici l'objet du vote] est rejeté. / est rejeté. L'article est adopté.''')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy if votes_by_deputy.get(x) == '-']
insert_string('Ont répondu non:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy if votes_by_deputy.get(x) == '+']
if deputes:
insert_string('Ont répondu oui:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
deputes = [deputies_dict[x] for x in votes_by_deputy if votes_by_deputy.get(x) == 'AB']
if deputes:
insert_string('Se sont abstenus:')
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
insert_string(self.format_list_of_deputes(deputes))
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
if missing_deputies:
# there were persons that could not be found, notify the user now
parentwin = doc.CurrentController.Frame.ContainerWindow
s = '\n'.join('%s (%s)' % x for x in missing_deputies)
MessageBox(parentwin,
'''Attention, des lignes n'ont pu être insérées:\n%s''' % s,
'Tabellio', 'infobox')
class InsertStandardTextDialog(unohelper.Base, XActionListener):
'''
Dialog to insert a standard snippet of text
'''
def __init__(self, ctx, doc):
self.ctx = ctx
self.doc = doc
def show(self):
smgr = self.ctx.ServiceManager
dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', self.ctx)
dialogModel.Width = 210
dialogModel.Height = 200
dialogModel.Title = "Insertion de texte standard"
self.snippets = SnippetDoc.values(self.ctx)
self.listbox = addWidget(dialogModel, 'listbox', 'ListBox', 5, 5, 200, 170)
self.listbox.StringItemList = tuple([x.title for x in self.snippets])
button = addWidget(dialogModel, 'insertButton', 'Button', 155, 180, 50, 14)
button.TabIndex = 0
button.DefaultButton = True
button.Label = 'Insérer'
button = addWidget(dialogModel, 'closeButton', 'Button', 95, 180, 50, 14)
button.TabIndex = 1
button.Label = 'Annuler'
# create the dialog control and set the model
controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', self.ctx)
controlContainer.setModel(dialogModel)
self.dialog = controlContainer
controlContainer.getControl('insertButton').setActionCommand('insert')
controlContainer.getControl('insertButton').addActionListener(self)
controlContainer.getControl('closeButton').addActionListener(self)
toolkit = smgr.createInstanceWithContext('com.sun.star.awt.ExtToolkit', self.ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
controlContainer.execute()
controlContainer.dispose()
def actionPerformed(self, actionEvent):
try:
if actionEvent.ActionCommand == 'insert':
snippets = SnippetDoc.values(self.ctx)
if self.listbox.SelectedItems:
snippet = self.snippets[self.listbox.SelectedItems[0]]
text = self.doc.Text
cursor = text.createTextCursorByRange(
self.doc.getCurrentController().getViewCursor().getStart())
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
inserts_base_url = oConfigAccess.getByName('InsertsRootURL')
if type(snippet.filename) is str:
snippet.filename = snippet.filename.encode('utf-8')
document_url = inserts_base_url + urllib.parse.quote(snippet.filename)
filename = download(self.ctx, document_url)
url = unohelper.systemPathToFileUrl(os.path.abspath(filename))
cursor.insertDocumentFromURL(url, ())
self.dialog.endExecute()
except:
display_exception(self.ctx)
class InsertStandardText(unohelper.Base, XJobExecutor):
'''
Job to display the dialog to insert a standard snippet of text
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.frame.Desktop", self.ctx )
doc = desktop.getCurrentComponent()
dialog = InsertStandardTextDialog(self.ctx, doc)
dialog.show()
except:
display_exception(self.ctx)
class ImportDalet(unohelper.Base, XJobExecutor):
'''
Job to insert a serie of documents from dalet
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
filepicker = self.ctx.ServiceManager.createInstance(
'com.sun.star.ui.dialogs.FilePicker')
filepicker.appendFilter('*.xml', '*.xml')
if filepicker.execute() == 0: # cancel
return
filenames = filepicker.getFiles()
if not filenames:
return
filename = normalize_filename(filenames[0])
doc = desktop.getCurrentComponent()
self.import_dalet(filename, doc)
except:
display_exception(self.ctx)
def import_dalet(self, filename, doc):
dalet = xml.dom.minidom.parseString(open(filename).read())
paths = []
for transcription in dalet.getElementsByTagName('FrenchTranscription'):
fullpath = transcription.getElementsByTagName('FullPath')[0]
clipcaption = get_text_node_content(
transcription.parentNode.getElementsByTagName('ClipCaption')[0])
document_url = get_text_node_content(fullpath)
if document_url.startswith('\\\\'):
# on a windows share
document_url = 'file://' + document_url[2:].replace('\\', '/')
paths.append((clipcaption, document_url))
paths.sort()
cursor = doc.Text.createTextCursor()
for caption, document_url in paths:
cursor.gotoEnd(False)
cursor.insertDocumentFromURL(document_url, ())
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
# insert the caption as a review note
cursor.setPropertyValue('CharBackColor', 16776960)
cursor.Text.insertString(cursor, '--- %s ---' % caption, False)
cursor.setPropertyValue('CharBackColor', -1)
cursor.Text.insertControlCharacter(cursor, APPEND_PARAGRAPH, False)
cursor.gotoEnd(False)
class Preview(unohelper.Base, XJobExecutor):
'''
Job to perform a preview of the current document
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
ctx = self.ctx
try:
smgr = self.ctx.ServiceManager
desktop = smgr.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx )
doc = desktop.getCurrentComponent()
errors = check_structure(doc)
if errors:
dialog = StructureCheckDialog(self.ctx, doc, errors,
continue_action = self.preview, continue_label = 'Plop')
dialog.show()
else:
self.preview(doc)
except:
display_exception(self.ctx)
def preview(self, doc):
parentwin = doc.CurrentController.Frame.ContainerWindow
# saving odt to local file
temp_file = os.path.join(tempfile.gettempdir(), 'preview.odt')
doc.storeToURL(unohelper.systemPathToFileUrl(temp_file), ())
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
href = oConfigAccess.getByName('PreviewServerURL')
try:
url = get_url_opener(self.ctx).open(href, data=open(temp_file, 'rb').read())
s = url.read()
except socket.timeout as e:
parentwin = doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin, 'Timeout sur le serveur', 'Alerte', 'infobox')
if url.headers['Content-type'] == 'text/plain':
# error
return MessageBox(parentwin, s, 'Alerte', 'infobox')
temp_pdf_file = os.path.join(tempfile.gettempdir(),
'tabellio-preview-%s.pdf' % random.randint(100000, 999999))
open(temp_pdf_file, 'wb').write(s)
if sys.platform.startswith('win'):
os.system('start file://%s' % temp_pdf_file)
else:
os.system('xdg-open %s &' % temp_pdf_file)
class ExportAsLegi(unohelper.Base, XJobExecutor):
'''
Job to export current document as legi
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
ctx = self.ctx
try:
smgr = self.ctx.ServiceManager
desktop = smgr.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx )
doc = desktop.getCurrentComponent()
errors = check_structure(doc)
if errors:
dialog = StructureCheckDialog(self.ctx, doc, errors,
continue_action = self.do_export, continue_label = 'Plop')
dialog.show()
else:
self.do_export(doc)
except:
display_exception(self.ctx)
def do_export(self, doc):
parentwin = doc.CurrentController.Frame.ContainerWindow
smgr = self.ctx.ServiceManager
filepicker = smgr.createInstance('com.sun.star.ui.dialogs.FilePicker')
filepicker.initialize((FILESAVE_SIMPLE,))
filepicker.appendFilter('*.legi', '*.legi')
if filepicker.execute() == 0: # cancel
return
filenames = filepicker.getFiles()
if not filenames:
return
filename = normalize_filename(filenames[0])
# saving odt to local file
temp_file = os.path.join(tempfile.gettempdir(), 'preview.odt')
doc.storeToURL(unohelper.systemPathToFileUrl(temp_file), ())
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
href = oConfigAccess.getByName('PreviewServerURL') + 'legi'
try:
url = get_url_opener(self.ctx).open(href, data = open(temp_file, 'rb').read())
s = url.read()
except socket.timeout as e:
parentwin = doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin, 'Timeout sur le serveur', 'Alerte', 'infobox')
if url.headers['Content-type'] == 'text/plain':
# error
return MessageBox(parentwin, s, 'Alerte', 'infobox')
open(filename, 'wb').write(s)
class PcfLogonDlg(unohelper.Base, XActionListener):
'''
Dialog to log on PCF document store
'''
def __init__(self, ctx, doc, logon_callback):
self.ctx = ctx
self.doc = doc
self.logon_callback = logon_callback
def display(self):
try:
smgr = self.ctx.ServiceManager
desktop = smgr.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
self.doc = doc = desktop.getCurrentComponent()
self.dialog = dialogModel = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialogModel', self.ctx)
dialogModel.Width = 130
dialogModel.Height = 55
dialogModel.Title = 'Authentification'
label = addWidget(dialogModel, 'usernameLabel', 'FixedText', 5, 5, 40, 15)
label.Label = 'Identifiant'
self.username = addWidget(dialogModel, 'username', 'Edit', 55, 2, 70, 14)
label = addWidget(dialogModel, 'passwordLabel', 'FixedText', 5, 20, 40, 15)
label.Label = 'Mot de passe'
self.password = addWidget(dialogModel, 'password', 'Edit', 55, 17, 70, 14)
self.password.EchoChar = 42
# login button
saveButton = addWidget(dialogModel, 'loginButton', 'Button', 75, 35, 50, 14)
saveButton.Label = "S'identifer"
saveButton.DefaultButton = True
# cancel button
cancelButton = addWidget(dialogModel, 'cancelButton', 'Button', 20, 35, 50, 14)
cancelButton.Label = 'Annuler'
# create the dialog control and set the model
self.dialog = controlContainer = smgr.createInstanceWithContext(
'com.sun.star.awt.UnoControlDialog', self.ctx)
controlContainer.setModel(dialogModel)
controlContainer.getControl('cancelButton').addActionListener(
CloseListener(controlContainer))
controlContainer.getControl('loginButton').addActionListener(self)
# create a peer
toolkit = smgr.createInstanceWithContext(
'com.sun.star.awt.ExtToolkit', self.ctx)
controlContainer.setVisible(False)
controlContainer.createPeer(toolkit, None)
# execute it
controlContainer.execute()
# dispose the dialog
controlContainer.dispose()
except:
display_exception(self.ctx)
def actionPerformed(self, actionEvent):
try:
self.dialog.endExecute()
cursor = self.doc.getCurrentController().getViewCursor()
# this was captured once between the Word Add-in and the
# tabellio procedure server
dauth = {'username': self.username.Text,
'password': self.password.Text}
data = 'verb=DispatchClassMethodXML&className=SERVICE&'\
'methodName=Logon&xmlRqst=%s' % urllib.parse.quote(
'<MLogon>'\
'<cvers>1,+0,+1,+0</cvers>'\
'<pwd>%(password)s</pwd>'\
'<usr>%(username)s</usr>'\
'</MLogon>' % dauth)
props = self.doc.DocumentProperties.getUserDefinedProperties()
post_url = props.getPropertyValue('documentUrl')
if not post_url:
parentwin = self.doc.CurrentController.Frame.ContainerWindow
error = "Métadonnée documentUrl absente du document, enregistrement impossible"
return MessageBox(parentwin, error, 'Alerte', 'errorbox')
href = urllib.parse.urlunsplit(urllib.parse.urlsplit(post_url)[:2] + (
'/xmldispatcher', '', ''))
try:
get_url_opener(self.ctx).open(href, data=data)
except (urllib.error.HTTPError, urllib.error.URLError) as e:
if hasattr(e, 'code') and e.code == 510:
s = e.fp.read()
if s.startswith('<error>'):
dom = xml.dom.minidom.parseString(s)
error = get_text_node_content(
dom.childNodes[0].getElementsByTagName('description')[0])
elif hasattr(e, 'code'):
error = "Erreur %s à l'authentification" % e.code
elif hasattr(e, 'reason'):
error = "Erreur à l'authentification (%s)" % e.reason
else:
error = "Erreur à l'authentification"
parentwin = self.doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin, error, 'Alerte', 'errorbox')
self.logon_callback(self.doc)
except:
display_exception(self.ctx)
class UploadLegi(unohelper.Base, XJobExecutor):
'''
Job to upload current file as legi to the server
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
try:
props = doc.DocumentProperties.getUserDefinedProperties()
post_url = props.getPropertyValue('documentUrl')
except: # com.sun.star.beans.UnknownPropertyException
parentwin = doc.CurrentController.Frame.ContainerWindow
return MessageBox(parentwin, "Pas d'adresse pour ce document.",
'Alerte', 'errorbox')
errors = check_structure(doc)
if errors:
dialog = StructureCheckDialog(self.ctx, doc, errors,
continue_action=self.do_check_auth, continue_label='Continuer')
dialog.show()
else:
self.do_check_auth(doc)
except:
display_exception(self.ctx)
def do_check_auth(self, doc):
if get_mode(self.ctx) == 'PCF':
dlg = PcfLogonDlg(self.ctx, doc, self.do_upload)
dlg.display()
else:
self.do_upload(doc)
def do_upload(self, doc):
parentwin = doc.CurrentController.Frame.ContainerWindow
try:
# saving odt to local file
temp_file = os.path.join(tempfile.gettempdir(), 'preview.odt')
doc.storeToURL(unohelper.systemPathToFileUrl(temp_file), ())
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
href = oConfigAccess.getByName('PreviewServerURL') + 'legi'
try:
url = get_url_opener(self.ctx).open(href, data=open(temp_file, 'rb').read())
as_legi_string = url.read()
except socket.timeout as e:
return MessageBox(parentwin, 'Timeout sur le serveur', 'Alerte', 'errorbox')
if url.headers['Content-type'] == 'text/plain':
# error
return MessageBox(parentwin, as_legi_string, 'Alerte', 'errorbox')
props = doc.DocumentProperties.getUserDefinedProperties()
post_url = props.getPropertyValue('documentUrl')
urlopener = get_url_opener(self.ctx)
request = urllib.request.Request(post_url, data=as_legi_string)
request.add_header('Content-Type', 'text/pcf-legi')
request.get_method = lambda: 'PUT'
try:
url = urlopener.open(request)
s = url.read()
except socket.timeout as e:
return MessageBox(parentwin, 'Timeout sur le serveur', 'Alerte', 'errorbox')
except urllib.error.URLError as e:
href = urllib.parse.urlsplit(post_url)[1]
error = "L'authentification sur le serveur (%s) a échoué." % href
return MessageBox(parentwin, error, 'Alerte', 'errorbox')
return MessageBox(parentwin,
'Document sauvegardé sur le serveur',
'Tabellio', 'infobox')
except:
display_exception(self.ctx)
class NewTabellioDocument(unohelper.Base, XJobExecutor):
'''
Job to create a new document based on the tabellio model
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
ctx = self.ctx
try:
smgr = self.ctx.ServiceManager
desktop = smgr.createInstanceWithContext('com.sun.star.frame.Desktop', self.ctx)
oConfigAccess = getConfigAccess(self.ctx,
'/org.entrouvert.openoffice.tabellio.Configuration', False)
inserts_base_url = oConfigAccess.getByName('InsertsRootURL')
document_url = inserts_base_url + 'tabellio.ott'
filename = download(ctx, document_url)
url = unohelper.systemPathToFileUrl(os.path.abspath(filename))
desktop.loadComponentFromURL(url, '_default', 0, () )
except:
display_exception(self.ctx)
class SwitchReviewNoteMode(unohelper.Base, XJobExecutor):
'''
Job to switch back and to review mode (a simple highlighting)
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
cursor = doc.getCurrentController().getViewCursor()
current_bg = cursor.getPropertyValue('CharBackColor')
if current_bg == -1:
# 16776960 is yellow (red:255 << 16 | green:255 << 8 | blue:0)
cursor.setPropertyValue('CharBackColor', 16776960)
else:
cursor.setPropertyValue('CharBackColor', -1)
class InsertFootnote(unohelper.Base, XJobExecutor):
'''
Job to insert a footnote
'''
def __init__(self, ctx):
self.ctx = ctx
def trigger(self, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
doc = desktop.getCurrentComponent()
dispatchHelper = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.DispatchHelper', self.ctx)
dispatchHelper.executeDispatch(
doc.getCurrentController().getFrame(),
'.uno:InsertFootnote',
'', 0, ())
except:
display_exception(self.ctx)
class DoNothing(unohelper.Base, XJobExecutor):
def __init__(self, ctx):
pass
def trigger(self, args):
pass
class Dispatcher(unohelper.Base, XDispatch, XControlNotificationListener, XJobExecutor,
XActionListener):
'''
Dispatcher necessary to handle complex controls in toolbars
'''
def __init__(self, ctx):
self.listeners = []
self.ctx = ctx
def addStatusListener(self, control, url):
if control not in self.listeners:
# keep track of listeners, for proper removal afterwards
self.listeners.append(control)
def removeStatusListener(self, control, url):
if control in self.listeners:
i = self.listeners.index(control)
del self.listeners[i]
def dispatch(self, url, args):
try:
debug_print('received dispatch instruction on', url.Path)
debug_print(' args:', args)
pass
except:
display_exception()
def controlEvent(self, event):
pass
def sendCommand(self, control, aUrl, command = None, args = None, enable = True):
try:
aEvent = FeatureStateEvent()
aEvent.FeatureURL = aUrl
aEvent.Source = self
aEvent.IsEnabled = enable
aEvent.Requery = False
if command:
aCtrlCmd = ControlCommand()
aCtrlCmd.Command = command
aCtrlCmd.Arguments = args
aEvent.State = aCtrlCmd
control.statusChanged(aEvent)
except:
display_exception()
class InsertDispatcher(Dispatcher):
'''
Dispatcher handling the insert deputy/minitre/... dropdowns in toolbars
'''
def __init__(self, ctx):
Dispatcher.__init__(self, ctx)
self.has_just_changed = False
self.lists = {}
self.deputy_letters = {}
self.controls = {}
self.ctx = ctx
def addStatusListener(self, control, url):
Dispatcher.addStatusListener(self, control, url)
try:
self.controls[url.Path] = control
if url.Path.startswith('depute'):
PARTS = 4 # four dropdown boxes
part = url.Path.split('_')[1]
parls = Deputy.values(self.ctx)
if not parls:
return
list_content = [x.get_name() for x in parls]
list_content.sort(lambda x, y: cmp(x.lower(), y.lower()))
naive_part_len = len(list_content) / PARTS
for i in range(PARTS):
if i == 0:
start_letter = 'a'
else:
start_letter = string.lowercase[
string.lowercase.index(list_content[i*naive_part_len][0].lower())+1]
start_letter = chr(ord(start_letter)-1)
if i == (PARTS-1):
end_letter = 'z'
else:
end_letter = string.lowercase[
string.lowercase.index(list_content[(i+1)*naive_part_len][0].lower())]
end_letter = chr(ord(end_letter)-1)
if int(part) == i+1:
break
list_content = [x for x in list_content if x[0].lower() >= start_letter and
x[0].lower() <= end_letter]
self.deputy_letters[part] = '%s->%s' % (start_letter, end_letter)
values = makeNamedList(tuple(list_content))
self.sendCommand(control, url, 'SetList', values)
self.set_dropdown_label(control, self.deputy_letters[part])
elif url.Path == 'ministres':
minsts = get_min_pres_menu_items(self.ctx)
list_content = [x.get_name() for x in minsts]
values = makeNamedList(tuple(list_content))
self.sendCommand(control, url, 'SetList', values)
self.set_dropdown_label(control, 'Ministres et Présidents')
elif url.Path == 'commissions':
elems = Commission.values(self.ctx)
list_content = [x.get_name() for x in elems]
values = makeNamedList(tuple(list_content))
self.sendCommand(control, url, 'SetList', values)
self.set_dropdown_label(control, 'Commissions')
else:
debug_print('Unknown Path:', url.Path)
except:
display_exception()
def set_dropdown_label(self, control, str):
aEvent = FeatureStateEvent()
aEvent.Source = self
aEvent.IsEnabled = True
aEvent.State = str
control.statusChanged(aEvent)
def dispatch(self, url, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
"com.sun.star.frame.Desktop", self.ctx )
doc = desktop.getCurrentComponent()
cursor = doc.getCurrentController().getViewCursor()
string = args[1].Value
if url.Path.startswith('depute') or url.Path == 'ministres':
text = doc.Text
text_cursor = text.createTextCursor()
text_cursor.gotoRange(cursor, False)
is_start_of_paragraph = text_cursor.isStartOfParagraph()
if url.Path.startswith('depute'):
parls = Deputy.values(self.ctx)
t = [x for x in parls if x.get_name() == string]
if not t:
raise Exception('Unknown Deputy')
parl = t[0]
if is_start_of_paragraph:
parl.insert_as_speaker(self.ctx, doc, cursor)
else:
parl.insert(self.ctx, doc, cursor)
elif url.Path == 'ministres':
minsts = get_min_pres_menu_items(self.ctx)
t = [x for x in minsts if x.get_name() == string]
if not t:
raise Exception('Unknown Ministres')
minist = t[0]
if is_start_of_paragraph:
minist.insert_as_speaker(self.ctx, doc, cursor)
else:
minist.insert(self.ctx, doc, cursor)
elif url.Path == 'commissions':
commissions = Commission.values(self.ctx)
t = [x for x in commissions if x.get_name() == string]
if not t:
raise Exception('Unknown Commission')
commis = t[0]
commis.insert(self.ctx, cursor)
self.set_dropdown_label(self.controls[url.Path], 'Commissions')
except:
display_exception()
finally:
if url.Path.startswith('depute'):
part = url.Path.split('_')[1]
self.set_dropdown_label(self.controls[url.Path], self.deputy_letters[part])
elif url.Path == 'ministres':
self.set_dropdown_label(self.controls[url.Path], 'Ministres et Présidents')
elif url.Path == 'commissions':
self.set_dropdown_label(self.controls[url.Path], 'Commissions')
# set focus to document
current_frame = desktop.getCurrentFrame()
current_frame.getComponentWindow().setFocus()
class Inserts(unohelper.Base, XJobExecutor, XDispatchProvider, XActionListener):
def __init__(self, ctx):
self.ctx = ctx
def queryDispatch(self, url, target, flags):
return InsertDispatcher(self.ctx)
class OnLoadEvent(unohelper.Base, XJob):
'''
Job executed when a document is loaded
'''
def __init__ (self, ctx):
self.ctx = ctx
def execute(self, args):
try:
desktop = self.ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.Desktop', self.ctx)
document = desktop.getCurrentComponent()
if document and document.supportsService('com.sun.star.text.TextDocument'):
layout_manager = document.CurrentController.Frame.LayoutManager
if 'Tabellio' in (document.DocumentProperties.Keywords or ''):
set_toolbar_visibility(self.ctx, True)
else:
set_toolbar_visibility(self.ctx, False)
else:
set_toolbar_visibility(self.ctx, False)
except:
display_exception(self.ctx)
# register everything to OpenOffice.org
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(
SpeakerDialog,
"org.entrouvert.openoffice.SpeakerDialog",
("com.sun.star.task.Job",),)
g_ImplementationHelper.addImplementation(
ManualSpeakerDialog,
"org.entrouvert.openoffice.ManualSpeakerDialog",
("com.sun.star.task.Job",),)
g_ImplementationHelper.addImplementation(
StyleApply,
"org.entrouvert.openoffice.StyleApply",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
ListStyleApply,
"org.entrouvert.openoffice.ListStyleApply",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
StructureCheck,
"org.entrouvert.openoffice.StructureCheck",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
ShowHideToolbars,
"org.entrouvert.openoffice.ShowHideToolbars",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
AboutDialog,
"org.entrouvert.openoffice.AboutDialog",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
ConfigurationDialog,
"org.entrouvert.openoffice.ConfigurationDialog",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
Preview,
"org.entrouvert.openoffice.Preview",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
ExportAsLegi,
"org.entrouvert.openoffice.ExportAsLegi",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
UploadLegi,
"org.entrouvert.openoffice.UploadLegi",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
NewTabellioDocument,
"org.entrouvert.openoffice.NewTabellioDocument",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
InsertVote,
"org.entrouvert.openoffice.InsertVote",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
InsertStandardText,
"org.entrouvert.openoffice.InsertStandardText",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
ImportDalet,
"org.entrouvert.openoffice.ImportDalet",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
SwitchReviewNoteMode,
"org.entrouvert.openoffice.SwitchReviewNoteMode",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
InsertFootnote,
"org.entrouvert.openoffice.InsertFootnote",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
DoNothing,
"org.entrouvert.openoffice.DoNothing",
("com.sun.star.task.Job",))
g_ImplementationHelper.addImplementation(
Inserts,
'org.entrouvert.openoffice.Inserts',
('com.sun.star.frame.DispatchProvider', 'com.sun.star.task.Job',))
g_ImplementationHelper.addImplementation(
OnLoadEvent,
'org.entrouvert.openoffice.OnLoadEvent',
('com.sun.star.task.Job',))