2673 lines
101 KiB
Python
2673 lines
101 KiB
Python
# -*- 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',))
|
||
|