# -*- 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. """ 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 or 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( ''\ '1,+0,+1,+0'\ '%(password)s'\ '%(username)s'\ '' % 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(''): 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',))