/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ package com.sun.star.wizards.agenda; import java.util.*; import com.sun.star.awt.TextEvent; import com.sun.star.beans.PropertyValue; import com.sun.star.container.NoSuchElementException; import com.sun.star.container.XIndexAccess; import com.sun.star.container.XNamed; import com.sun.star.document.XDocumentProperties; import com.sun.star.frame.XComponentLoader; import com.sun.star.frame.XTerminateListener; import com.sun.star.i18n.NumberFormatIndex; import com.sun.star.lang.Locale; import com.sun.star.lang.WrappedTargetException; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.table.XCell; import com.sun.star.table.XTableRows; import com.sun.star.text.*; import com.sun.star.uno.Any; import com.sun.star.uno.UnoRuntime; import com.sun.star.util.XNumberFormatsSupplier; import com.sun.star.util.XNumberFormatter; import com.sun.star.util.XSearchDescriptor; import com.sun.star.util.XSearchable; import com.sun.star.wizards.common.FileAccess; import com.sun.star.wizards.common.Helper; import com.sun.star.wizards.common.JavaTools; import com.sun.star.wizards.common.NumberFormatter; import com.sun.star.wizards.common.PropertyNames; import com.sun.star.wizards.document.OfficeDocument; import com.sun.star.wizards.text.TextDocument; import com.sun.star.wizards.text.TextSectionHandler; import com.sun.star.wizards.ui.UnoDialog2; import com.sun.star.wizards.ui.event.DataAware; /** * * The classes here implement the whole document-functionality of the agenda wizard: * the live-preview and the final "creation" of the document, when the user clicks "finish".
*
*

Some terminology:

* items are names or headings. we don't make any distinction. * *
* The Agenda Template is used as general "controller" of the whole document, whereas the * two child-classes ItemsTable and TopicsTable control the item tables (note plural!) and the * topics table (note singular). *

* Other small classes are used to abstract the handling of cells and text and we * try to use them as components. *

* We tried to keep the Agenda Template as flexible as possible, though there * must be many limitations, because it is generated dynamically.

* To keep the template flexible the following decisions were made:
* 1. Item tables.
* 1.a. there might be arbitrary number of Item tables.
* 1.b. Item tables design (bordewr, background) is arbitrary.
* 1.c. Items text styles are individual, and use stylelist styles with predefined names.
* As result the following limitations:
* Pairs of Name->value for each item.
* Tables contain *only* those pairs.
* 2. Topics table.
* 2.a. arbitrary structure.
* 2.b. design is arbitrary.
* As result the following limitations:
* No column merge is allowed.
* One compulsory Heading row.
*

* To let the template be flexible, we use a kind of "detection": we look where * the items are read the design of each table, reapplying it after writing the * table. *

* A note about threads:
* Many methods here are synchronized, in order to avoid collision made by * events fired too often. * @author rpiterman * */ public class AgendaTemplate extends TextDocument implements TemplateConsts, DataAware.Listener { /** * resources. */ AgendaWizardDialogResources resources; /** * data model. This keeps the status of the agenda document, and * every redraw is done according to this data. * Exception: topic data is written programmatically, event-oriented. */ CGAgenda agenda; /** * the UNO Text Document service */ Object document; /** * Service Factory */ XMultiServiceFactory docMSF; /** * The template-filename of the current template. * Since we often re-link section and the break the link, * in order to restore them, we need a template to link to. * This is practically an identical copy of the current template. */ String template; /** * used for common operations on sections. */ TextSectionHandler textSectionHandler; /** * a component loader. */ XComponentLoader xComponentLoader; /** * an array containing all ItemTable object (which control each an Items * Table in the document. */ ItemsTable[] itemsTables; /** * the controller of the topics table. */ Topics topics; /** * Stores reusable AOO Placeholder TextFields to insert to the document. */ Map itemsCache; /** * This map is used to find which tables contains a certain Item, so * the keys are the different Items, the Objects are the ItemTable controllers. * When an Item must be redrawn (because the user checked or unchecked it), * the controller is retrieved from this Map, and a redraw is issued on this controller. */ Map itemsMap = new Hashtable(11); /** * A temporary variable used to list all items and map them. */ List _allItems = new Vector(); /** * keep a reference on some static items in the document, * so when their content is changed (through the user), we * can just reference them and set their text. */ TextElement teTitle, teDate, teTime, teLocation; XTextRange trTitle, trDate, trTime, trLocation; /** * used to format the date / time. */ int dateFormat, timeFormat; XNumberFormatter dateFormatter, timeFormatter; /** * used to transfer time from VCL to UNO. */ long docNullTime; Calendar calendar; /** * used to set the document title property (step 6). */ private XDocumentProperties m_xDocProps; /** * loads the given template, and analyze its structure. * @param templateURL * @param topics * @see AgendaTemplate.initialize() * @see AgendaTemplate.initializeData() */ public synchronized void load(String templateURL, List topics) { template = calcTemplateName(templateURL); document = loadAsPreview(templateURL, false); docMSF = UnoRuntime.queryInterface(XMultiServiceFactory.class, document); xFrame.getComponentWindow().setEnable(false); xTextDocument.lockControllers(); initialize(); initializeData(topics); xTextDocument.unlockControllers(); } /** * The agenda templates are in format of aw-XXX.ott * the templates name is then XXX.ott. * This method calculates it. * @param url * @return the template name without the "aw-" at the beginning. */ private String calcTemplateName(String url) { return FileAccess.connectURLs(FileAccess.getParentDir(url), FileAccess.getFilename(url).substring(3)); } /** * synchronize the document to the model.
* this method rewrites all titles, item tables , and the topics table- * thus synchronizing the document to the data model (CGAgenda). * @param topicsData since the model does not contain Topics * information (it is only actualized on save) the given list * supplies this information. */ private void initializeData(List topicsData) { for (int i = 0; i < itemsTables.length; i++) { try { itemsTables[i].write(PropertyNames.EMPTY_STRING); } catch (Exception ex) { ex.printStackTrace(); } } redrawTitle("txtTitle"); redrawTitle("txtDate"); redrawTitle("txtTime"); redrawTitle("cbLocation"); topics.writeAll(topicsData); setTemplateTitle(agenda.cp_TemplateName); } /** * redraws/rewrites the table which contains the given item * This method is called when the user checks/unchecks an item. * The table is being found, in which the item is, and redrawn. * @param itemName */ public synchronized void redraw(String itemName) { try { // get the table in which the item is... Object itemsTable = itemsMap.get(itemName); // rewrite the table. ((ItemsTable) itemsTable).write(null); } catch (Exception e) { e.printStackTrace(); } } /** * update the documents title property to the given title * @param newTitle new title. */ synchronized void setTemplateTitle(String newTitle) { m_xDocProps.setTitle(newTitle); } /** * constructor. The document is *not* loaded here. * only some formal members are set. * @param xmsf_ service factory. * @param agenda_ the data model (CGAgenda) * @param resources_ resources. */ AgendaTemplate(XMultiServiceFactory xmsf_, CGAgenda agenda_, AgendaWizardDialogResources resources_, XTerminateListener listener) { super(xmsf_, listener, "WIZARD_LIVE_PREVIEW"); agenda = agenda_; resources = resources_; if (itemsCache == null) { initItemsCache(); } _allItems = null; } /** * checks the data model if the * item corresponding to the given string should be shown * @param itemName a string representing an Item (name or heading). * @return true if the model specifies that the item should be displayed. */ boolean isShowItem(String itemName) { if (itemName.equals(FILLIN_MEETING_TYPE)) { return agenda.cp_ShowMeetingType; } else if (itemName.equals(FILLIN_READ)) { return agenda.cp_ShowRead; } else if (itemName.equals(FILLIN_BRING)) { return agenda.cp_ShowBring; } else if (itemName.equals(FILLIN_NOTES)) { return agenda.cp_ShowNotes; } else if (itemName.equals(FILLIN_FACILITATOR)) { return agenda.cp_ShowFacilitator; } else if (itemName.equals(FILLIN_TIMEKEEPER)) { return agenda.cp_ShowTimekeeper; } else if (itemName.equals(FILLIN_NOTETAKER)) { return agenda.cp_ShowNotetaker; } else if (itemName.equals(FILLIN_PARTICIPANTS)) { return agenda.cp_ShowAttendees; } else if (itemName.equals(FILLIN_CALLED_BY)) { return agenda.cp_ShowCalledBy; } else if (itemName.equals(FILLIN_OBSERVERS)) { return agenda.cp_ShowObservers; } else if (itemName.equals(FILLIN_RESOURCE_PERSONS)) { return agenda.cp_ShowResourcePersons; } else { throw new IllegalArgumentException("No such item"); } } /** * itemsCache is a Map containing all agenda item. These are object which * "write themselves" to the table, given a table cursor. * A cache is used in order to reuse the objects, instead of recreate them. * This method fills the cache will all items objects (names and headings). */ private void initItemsCache() { itemsCache = new Hashtable(11); XMultiServiceFactory xmsf = UnoRuntime.queryInterface(XMultiServiceFactory.class, document); // Headings itemsCache.put(FILLIN_MEETING_TYPE, new AgendaItem(FILLIN_MEETING_TYPE, new TextElement(resources.itemMeetingType, STYLE_MEETING_TYPE), new PlaceholderElement(STYLE_MEETING_TYPE_TEXT, resources.reschkMeetingTitle_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_BRING, new AgendaItem(FILLIN_BRING, new TextElement(resources.itemBring, STYLE_BRING), new PlaceholderElement(STYLE_BRING_TEXT, resources.reschkBring_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_READ, new AgendaItem(FILLIN_READ, new TextElement(resources.itemRead, STYLE_READ), new PlaceholderElement(STYLE_READ_TEXT, resources.reschkRead_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_NOTES, new AgendaItem(FILLIN_NOTES, new TextElement(resources.itemNote, STYLE_NOTES), new PlaceholderElement(STYLE_NOTES_TEXT, resources.reschkNotes_value, resources.resPlaceHolderHint, xmsf))); // Names itemsCache.put(FILLIN_CALLED_BY, new AgendaItem(FILLIN_CALLED_BY, new TextElement(resources.itemCalledBy, STYLE_CALLED_BY), new PlaceholderElement(STYLE_CALLED_BY_TEXT, resources.reschkConvenedBy_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_FACILITATOR, new AgendaItem(FILLIN_FACILITATOR, new TextElement(resources.itemFacilitator, STYLE_FACILITATOR), new PlaceholderElement(STYLE_FACILITATOR_TEXT, resources.reschkPresiding_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_PARTICIPANTS, new AgendaItem(FILLIN_PARTICIPANTS, new TextElement(resources.itemAttendees, STYLE_PARTICIPANTS), new PlaceholderElement(STYLE_PARTICIPANTS_TEXT, resources.reschkAttendees_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_NOTETAKER, new AgendaItem(FILLIN_NOTETAKER, new TextElement(resources.itemNotetaker, STYLE_NOTETAKER), new PlaceholderElement(STYLE_NOTETAKER_TEXT, resources.reschkNoteTaker_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_TIMEKEEPER, new AgendaItem(FILLIN_TIMEKEEPER, new TextElement(resources.itemTimekeeper, STYLE_TIMEKEEPER), new PlaceholderElement(STYLE_TIMEKEEPER_TEXT, resources.reschkTimekeeper_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_OBSERVERS, new AgendaItem(FILLIN_OBSERVERS, new TextElement(resources.itemObservers, STYLE_OBSERVERS), new PlaceholderElement(STYLE_OBSERVERS_TEXT, resources.reschkObservers_value, resources.resPlaceHolderHint, xmsf))); itemsCache.put(FILLIN_RESOURCE_PERSONS, new AgendaItem(FILLIN_RESOURCE_PERSONS, new TextElement(resources.itemResource, STYLE_RESOURCE_PERSONS), new PlaceholderElement(STYLE_RESOURCE_PERSONS_TEXT, resources.reschkResourcePersons_value, resources.resPlaceHolderHint, xmsf))); } /** * Initializes a template.
* This method does the following tasks:
* Get a Time and Date format for the document, and retrieve the null date of the document (which is * document-specific).
* Initializes the Items Cache map. * Analyses the document:
* -find all "fille-ins" (appear as >xxx< in the document). * -analyze all items sections (and the tables in them). * -locate the titles and actualize them * -analyze the topics table */ private void initialize() { /* * Get the default locale of the document, and create the date and time formatters. */ XMultiServiceFactory docMSF = UnoRuntime.queryInterface(XMultiServiceFactory.class, document); try { Object defaults = docMSF.createInstance("com.sun.star.text.Defaults"); Locale l = (Locale) Helper.getUnoStructValue(defaults, "CharLocale"); java.util.Locale jl = new java.util.Locale( l.Language, l.Country, l.Variant); calendar = Calendar.getInstance(jl); XNumberFormatsSupplier nfs = UnoRuntime.queryInterface(XNumberFormatsSupplier.class, document); Object formatSettings = nfs.getNumberFormatSettings(); com.sun.star.util.Date date = (com.sun.star.util.Date) Helper.getUnoPropertyValue(formatSettings, "NullDate"); calendar.set(date.Year, date.Month - 1, date.Day); docNullTime = JavaTools.getTimeInMillis(calendar); dateFormat = NumberFormatter.getNumberFormatterKey(nfs, NumberFormatIndex.DATE_SYSTEM_LONG); timeFormat = NumberFormatter.getNumberFormatterKey(nfs, NumberFormatIndex.TIME_HHMM); dateFormatter = NumberFormatter.createNumberFormatter(xMSF, nfs); timeFormatter = NumberFormatter.createNumberFormatter(xMSF, nfs); } catch (Exception ex) { ex.printStackTrace(); throw new NullPointerException("Fatal Error: could not initialize locale or date/time formats."); } /* * get the document properties object. */ m_xDocProps = OfficeDocument.getDocumentProperties(document); initItemsCache(); initializeItems(); initializeTitles(); initializeItemsSections(); XMultiServiceFactory xMultiServiceFactory = UnoRuntime.queryInterface(XMultiServiceFactory.class, document); textSectionHandler = new TextSectionHandler(xMultiServiceFactory, UnoRuntime.queryInterface(XTextDocument.class, document)); initializeTopics(); _allItems.clear(); _allItems = null; } /** * locates the titles (name, location, date, time) and saves a reference to their Text ranges. * */ private void initializeTitles() { XTextRange item = null; XMultiServiceFactory xmsf = UnoRuntime.queryInterface(XMultiServiceFactory.class, document); for (int i = 0; i < _allItems.size(); i++) { item = (XTextRange) _allItems.get(i); String text = item.getString().trim().toLowerCase(); if (text.equals(FILLIN_TITLE)) { teTitle = new PlaceholderTextElement(item, resources.resPlaceHolderTitle, resources.resPlaceHolderHint, xmsf); trTitle = item; _allItems.remove(i--); } else if (text.equals(FILLIN_DATE)) { teDate = new PlaceholderTextElement(item, resources.resPlaceHolderDate, resources.resPlaceHolderHint, xmsf); trDate = item; _allItems.remove(i--); } else if (text.equals(FILLIN_TIME)) { teTime = new PlaceholderTextElement(item, resources.resPlaceHolderTime, resources.resPlaceHolderHint, xmsf); trTime = item; _allItems.remove(i--); } else if (text.equals(FILLIN_LOCATION)) { teLocation = new PlaceholderTextElement(item, resources.resPlaceHolderLocation, resources.resPlaceHolderHint, xmsf); trLocation = item; _allItems.remove(i--); } } } private void initializeTopics() { topics = new Topics(); } private void initializeItems() { _allItems = searchFillInItems(); } /** * searches the document for items in the format ">*<" * @return a vector containing the XTextRanges of the found items */ private List searchFillInItems() { try { XSearchable xSearchable = UnoRuntime.queryInterface(XSearchable.class, document); XSearchDescriptor sd = xSearchable.createSearchDescriptor(); sd.setSearchString("<[^>]+>"); sd.setPropertyValue("SearchRegularExpression", Boolean.TRUE); sd.setPropertyValue("SearchWords", Boolean.TRUE); XIndexAccess ia = xSearchable.findAll(sd); List l = new ArrayList(ia.getCount()); for (int i = 0; i < ia.getCount(); i++) { try { l.add(UnoRuntime.queryInterface(XTextRange.class, ia.getByIndex(i))); } catch (Exception ex) { System.err.println("Nonfatal Error in finding fillins."); } } return l; } catch (Exception ex) { ex.printStackTrace(); throw new IllegalArgumentException("Fatal Error: Loading template failed: searching fillins failed"); } } /** * analyze the item sections in the template. delegates the analyze of each table to the * ItemsTable class. */ private void initializeItemsSections() { String[] sections = getSections(document, TemplateConsts.SECTION_ITEMS); // for each section - there is a table... itemsTables = new ItemsTable[sections.length]; for (int i = 0; i < itemsTables.length; i++) { try { itemsTables[i] = new ItemsTable(getSection(sections[i]), getTable(sections[i])); } catch (Exception ex) { ex.printStackTrace(); throw new IllegalArgumentException("Fatal Error while initialilzing Template: items table in section " + sections[i]); } } } private String[] getSections(Object document, String s) { XTextSectionsSupplier xTextSectionsSupplier = UnoRuntime.queryInterface(XTextSectionsSupplier.class, document); String[] allSections = xTextSectionsSupplier.getTextSections().getElementNames(); return getNamesWhichStartWith(allSections, s); } Object getSection(String name) throws NoSuchElementException, WrappedTargetException { XTextSectionsSupplier xTextSectionsSupplier = UnoRuntime.queryInterface(XTextSectionsSupplier.class, document); return ((Any) (xTextSectionsSupplier.getTextSections().getByName(name))).getObject(); } Object getTable(String name) throws NoSuchElementException, WrappedTargetException { XTextTablesSupplier xTextTablesSupplier = UnoRuntime.queryInterface(XTextTablesSupplier.class, document); return ((Any) xTextTablesSupplier.getTextTables().getByName(name)).getObject(); } /** * implementation of DataAware.Listener, is * called when title/date/time or location are * changed. */ public synchronized void eventPerformed(Object param) { TextEvent te = (TextEvent) param; String controlName = (String) Helper.getUnoPropertyValue( UnoDialog2.getModel(te.Source), PropertyNames.PROPERTY_NAME); redrawTitle(controlName); } private synchronized void redrawTitle(String controlName) { if (controlName.equals("txtTitle")) { writeTitle(teTitle, trTitle, agenda.cp_Title); } else if (controlName.equals("txtDate")) { writeTitle(teDate, trDate, getDateString(agenda.cp_Date)); } else if (controlName.equals("txtTime")) { writeTitle(teTime, trTime, getTimeString(agenda.cp_Time)); } else if (controlName.equals("cbLocation")) { writeTitle(teLocation, trLocation, agenda.cp_Location); } else { throw new IllegalArgumentException("No such title control..."); } } private void writeTitle(TextElement te, XTextRange tr, String text) { te.text = (text == null ? PropertyNames.EMPTY_STRING : text); te.write(tr); } private static long DAY_IN_MILLIS = (24 * 60 * 60 * 1000); private String getDateString(String d) { if (d == null || d.equals(PropertyNames.EMPTY_STRING)) { return PropertyNames.EMPTY_STRING; } int date = Integer.parseInt(d); calendar.clear(); calendar.set(date / 10000, (date % 10000) / 100 - 1, date % 100); long date1 = JavaTools.getTimeInMillis(calendar); /* * docNullTime and date1 are in millis, but * I need a day... */ double daysDiff = (date1 - docNullTime) / DAY_IN_MILLIS + 1; return dateFormatter.convertNumberToString(dateFormat, daysDiff); } private String getTimeString(String s) { if (s == null || s.equals(PropertyNames.EMPTY_STRING)) { return PropertyNames.EMPTY_STRING; } int time = Integer.parseInt(s); double t = ((double) (time / 1000000) / 24) + ((double) ((time % 1000000) / 1000) / (24 * 60)); return timeFormatter.convertNumberToString(timeFormat, t); } /* ******************************************* * F I N I S H *********************************************/ /** the user clicked finish **/ public synchronized void finish(List topics) { createMinutes(topics); deleteHiddenSections(); textSectionHandler.removeAllTextSections(); } /** * hidden sections exist when an item's section is hidden because the * user specified not to display any items which it contains. * When finishing the wizard removes this sections entirely from the document. */ private void deleteHiddenSections() { XTextSectionsSupplier xTextSectionsSupplier = UnoRuntime.queryInterface(XTextSectionsSupplier.class, document); String[] allSections = xTextSectionsSupplier.getTextSections().getElementNames(); try { for (int i = 0; i < allSections.length; i++) { Object section = getSection(allSections[i]); //Try3.showProps(section); boolean visible = ((Boolean) Helper.getUnoPropertyValue(section, "IsVisible")).booleanValue(); if (!visible) { UnoRuntime.queryInterface(XTextContent.class, section).getAnchor().setString(PropertyNames.EMPTY_STRING); } } } catch (Exception ex) { ex.printStackTrace(); } } /** * create the minutes for the given topics or remove the minutes section from the document. * If no topics are supplied, or the user * specified not to create minutes, the minutes section will be removed, * @param topicsData supplies PropertyValue arrays containing the values for the topics. */ public synchronized void createMinutes(List topicsData) { // if the minutes section should be removed (the // user did not check "create minutes") if (!agenda.cp_IncludeMinutes || (topicsData.size() <= 1)) { try { Object minutesAllSection = getSection(SECTION_MINUTES_ALL); XTextSection xTextSection = UnoRuntime.queryInterface(XTextSection.class, minutesAllSection); xTextSection.getAnchor().setString(PropertyNames.EMPTY_STRING); } catch (Exception ex) { ex.printStackTrace(); } } // the user checked "create minutes" else { try { String itemText; XTextRange item; int topicStartTime = 0; try { topicStartTime = Integer.parseInt(agenda.cp_Time); } catch (Exception ex) { } String time; // first I replace the minutes titles... List items = searchFillInItems(); for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) { item = (XTextRange) items.get(itemIndex); itemText = item.getString().trim().toLowerCase(); if (itemText.equals(FILLIN_MINUTES_TITLE)) { fillMinutesItem(item, agenda.cp_Title, resources.resPlaceHolderTitle); } else if (itemText.equals(FILLIN_MINUTES_LOCATION)) { fillMinutesItem(item, agenda.cp_Location, resources.resPlaceHolderLocation); } else if (itemText.equals(FILLIN_MINUTES_DATE)) { fillMinutesItem(item, getDateString(agenda.cp_Date), resources.resPlaceHolderDate); } else if (itemText.equals(FILLIN_MINUTES_TIME)) { fillMinutesItem(item, getTimeString(agenda.cp_Time), resources.resPlaceHolderTime); } } items.clear(); /* * now add minutes for each topic. * The template contains *one* minutes section, so * we first use the one available, and then add a new one... * * topics data has *always* an empty topic at the end... */ for (int i = 0; i < topicsData.size() - 1; i++) { PropertyValue[] topic = (PropertyValue[]) topicsData.get(i); items = searchFillInItems(); for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) { item = (XTextRange) items.get(itemIndex); itemText = item.getString().trim().toLowerCase(); if (itemText.equals(FILLIN_MINUTE_NUM)) { fillMinutesItem(item, topic[0].Value, PropertyNames.EMPTY_STRING); } else if (itemText.equals(FILLIN_MINUTE_TOPIC)) { fillMinutesItem(item, topic[1].Value, PropertyNames.EMPTY_STRING); } else if (itemText.equals(FILLIN_MINUTE_RESPONSIBLE)) { fillMinutesItem(item, topic[2].Value, PropertyNames.EMPTY_STRING); } else if (itemText.equals(FILLIN_MINUTE_TIME)) { int topicTime = 0; try { topicTime = (new Integer((String) topic[3].Value)).intValue(); } catch (Exception ex) { } // if the topic has no time, we do not display any time here. if (topicTime == 0 || topicStartTime == 0) { time = (String) topic[3].Value; } else { time = getTimeString(String.valueOf(topicStartTime)) + " - "; topicStartTime += topicTime * 1000; time += getTimeString(String.valueOf(topicStartTime)); } fillMinutesItem(item, time, PropertyNames.EMPTY_STRING); } } textSectionHandler.removeTextSectionbyName(SECTION_MINUTES); // after the last section we do not insert a new one. if (i < topicsData.size() - 2) { textSectionHandler.insertTextSection(SECTION_MINUTES, template, false); } } } catch (Exception ex) { ex.printStackTrace(); } } } /** * given a text range and a text, fills the given * text range with the given text. * If the given text is empty, uses a placeholder with the given placeholder text. * @param range text range to fill * @param text the text to fill to the text range object. * @param placeholder the placeholder text to use, if the text argument is empty (null or PropertyNames.EMPTY_STRING) */ private void fillMinutesItem(XTextRange range, Object text, String placeholder) { String paraStyle = (String) Helper.getUnoPropertyValue(range, "ParaStyleName"); range.setString((String) text); Helper.setUnoPropertyValue(range, "ParaStyleName", paraStyle); if (text == null || text.equals(PropertyNames.EMPTY_STRING)) { if (placeholder != null && !placeholder.equals(PropertyNames.EMPTY_STRING)) { XTextContent placeHolder = createPlaceHolder(docMSF, placeholder, resources.resPlaceHolderHint); try { range.getStart().getText().insertTextContent(range.getStart(), placeHolder, true); } catch (Exception ex) { ex.printStackTrace(); } } } } /** * creates a placeholder field with the given text and given hint. * @param xmsf service factory * @param ph place holder text * @param hint hint text * @return the place holder field. */ public static XTextContent createPlaceHolder(XMultiServiceFactory xmsf, String ph, String hint) { Object placeHolder; try { placeHolder = xmsf.createInstance("com.sun.star.text.TextField.JumpEdit"); } catch (Exception ex) { ex.printStackTrace(); return null; } Helper.setUnoPropertyValue(placeHolder, "PlaceHolder", ph); Helper.setUnoPropertyValue(placeHolder, "Hint", hint); Helper.setUnoPropertyValue(placeHolder, "PlaceHolderType", new Short(PlaceholderType.TEXT)); return UnoRuntime.queryInterface(XTextContent.class, placeHolder); } /* * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ * ================================= * The ItemTable class * ================================= * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ */ public class ItemsTable { Object table; Object section; /** * the items in the table. */ List items = new Vector(6); public ItemsTable(Object section_, Object table_) { table = table_; section = section_; AgendaItem ai; XTextRange item; String iText; /* go through all <*> items in the document * and each one if it is in this table. * If they are, register them to belong here, notice their order * and remove them from the list of all <*> items, so the next * search will be faster. */ for (int i = 0; i < _allItems.size(); i++) { item = (XTextRange) _allItems.get(i); Object t = Helper.getUnoPropertyValue(item, "TextTable"); if ((t instanceof Any) && ((Any) t).getObject() == table) { iText = item.getString().toLowerCase().trim(); ai = (AgendaItem) itemsCache.get(item.getString().toLowerCase().trim()); if (ai != null) { items.add(ai); _allItems.remove(i--); itemsMap.put(iText, this); } } } } /** * link the section to the template. this will restore the original table * with all the items.
* then break the link, to make the section editable.
* then, starting at cell one, write all items that should be visible. * then clear the rest and remove obsolete rows. * If no items are visible, hide the section. * @param dummy we need a param to make this an Implementation of AgendaElement. * @throws Exception */ public synchronized void write(Object dummy) throws Exception { synchronized(this) { String name = getName(section); // link and unlink the section to the template. textSectionHandler.linkSectiontoTemplate(section, template, name); textSectionHandler.breakLinkOfTextSection(section); // we need to get a new instance after linking. table = getTable(name); section = getSection(name); XTextTable xTextTable = UnoRuntime.queryInterface(XTextTable.class, table); XTextTableCursor cursor = xTextTable.createCursorByCellName("A1"); AgendaItem ai; // should this section be visible? boolean visible = false; // write items // =========== String cellName = PropertyNames.EMPTY_STRING; /* now go through all items that belong to this * table. Check each one against the model. If it should * be display, call it's write method. * All items are of type AgendaItem which means they write * two cells to the table: a title (text) and a placeholder. * see AgendaItem class below. */ for (int i = 0; i < items.size(); i++) { ai = (AgendaItem) items.get(i); if (isShowItem(ai.name)) { visible = true; ai.table = table; ai.write(cursor); // I store the cell name which was last written... cellName = cursor.getRangeName(); cursor.goRight((short) 1, false); } } Helper.setUnoPropertyValue(section, "IsVisible", visible ? Boolean.TRUE : Boolean.FALSE); if (!visible) { return; /* remove obsolete rows * ==================== * if the cell that was last written is the current cell, * it means this is the end of the table, so we end here. * (because after getting the cellName above, I call the goRight method. * If it did not go right, it means its the last cell. */ } if (cellName.equals(cursor.getRangeName())) { return; /* * if not, we continue and clear all cells until we are at the end of the row. */ } Object cell; while ((!cellName.equals(cursor.getRangeName()) && (!cursor.getRangeName().startsWith("A")))) { cell = xTextTable.getCellByName(cursor.getRangeName()); UnoRuntime.queryInterface(XTextRange.class, cell).setString(PropertyNames.EMPTY_STRING); cellName = cursor.getRangeName(); cursor.goRight((short) 1, false); } /* * again: if we are at the end of the table, end here. */ if (cellName.equals(cursor.getRangeName())) { return; } int rowIndex = getRowIndex(cursor); int rowsCount = getRowCount(UnoRuntime.queryInterface(XTextTable.class, table)); /* now before deleting I move the cursor up so it * does not disappear, because it will crash office. */ cursor.gotoStart(false); if (rowsCount >= rowIndex) { removeTableRows(table, rowIndex - 1, (rowsCount - rowIndex) + 1); } } } } /* * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ * ================================= * The Topics class * ================================= * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ */ /** * This class handles the preview of the topics table. * You can call it the controller of the topics table. * It differs from ItemsTable in that it has no data model - * the update is done programmatically.
*
* The decision to make this class a class by its own * was done out of logic reasons and not design/functionality reasons, * since there is anyway only one instance of this class at runtime * it could have also be implemented in the AgendaTemplate class * but for clarity and separation I decided to make a subclass for it. * * @author rp143992 */ public class Topics { /** * the topics table */ XTextTable table; /** * A List of Cell Formatters for the first row. */ List firstRowFormat = new Vector(); /** * A List of Cell Formatters for the last row. * (will contain them in reverse order) */ List lastRowFormat = new Vector(); /** * the format of the cell of each topic cell. */ List topicCellFormats = new Vector(); /** * for each topic cell there is * a member in this vector */ List topicCells = new Vector(); int rowsPerTopic; /** * fields which hold the number of the * fillins in the cells vectors. */ int numCell = -1; int topicCell = -1; int responsibleCell = -1; int timeCell = -1; /** * this is a list which traces which topics were written to the document * and which not. When a cell needs to be actualized, it is checked that the * whole topic is already present in the document, using this vector. * The vector contains nulls for topics which were not written, and * empty strings for topics which were written (though any other * object would also do - i check only if it is a null or not...); */ List writtenTopics = new Vector(); /** * Analyze the structure of the Topics table. * The structure Must be as follows:
* -One Header Row.
* -arbitrary number of rows per topic
* -arbitrary content in the topics row
* -only soft formatting will be restored.
* -the topic rows must repeat three times.
* -in the topics rows, placeholders for number, topic, responsible, and duration * must be placed.
*
* A word about table format: to reconstruct the format of the * table we hold to the following formats: first row (header), topic, and last row. * We hold the format of the last row, because one might wish to give it * a special format, other than the one on the bottom of each topic. * The left and right borders of the whole table are, on the other side, * part of the topics rows format, and need not be preserved separately. */ public Topics() { Object t; Map topicItems = new Hashtable(4); // This is the topics table. say hallo :-) try { t = getTable(SECTION_TOPICS); } catch (Exception ex) { ex.printStackTrace(); throw new IllegalArgumentException("Fatal error while loading template: table " + SECTION_TOPICS + " could not load."); } // and this is the XTable. table = UnoRuntime.queryInterface(XTextTable.class, t); /* first I store all <*> ranges * which are in the topics table. * I store each <*> range in this - the key * is the cell it is in. Later when analyzing the topic, * cell by cell, I check in this map to know * if a cell contains a <*> or not. */ Hashtable items = new Hashtable(); XTextRange item; Object cell; for (int i = 0; i < _allItems.size(); i++) { item = (XTextRange) _allItems.get(i); t = Helper.getUnoPropertyValue(item, "TextTable"); if ((t instanceof Any) && ((Any) t).getObject() == table) { cell = Helper.getUnoPropertyValue(item, "Cell"); items.put(((Any) cell).getObject(), item); } } /* * in the topics table, there are always one * title row and three topics defined. * So no mutter how many rows a topic takes - we * can restore its structure and format. */ int rows = getRowCount(table); rowsPerTopic = (rows - 1) / 3; String firstCell = "A" + (1 + rowsPerTopic + 1); String afterLastCell = "A" + (1 + (rowsPerTopic * 2) + 1); // go to the first row of the 2. topic XTextTableCursor cursor = table.createCursorByCellName(firstCell); XTextRange range; // analyze the structure of the topic rows. while (!cursor.getRangeName().equals(afterLastCell)) { cell = table.getCellByName(cursor.getRangeName()); XTextRange xTextRange = UnoRuntime.queryInterface(XTextRange.class, cell); // first I store the content and para style of the cell AgendaElement ae = new TextElement(xTextRange); // if the cell contains a relevant <...> // i add the text element to the hash, // so it's text can be updated later. range = (XTextRange) items.get(cell); if (range != null) { topicItems.put(xTextRange.getString().toLowerCase().trim(), ae); } topicCells.add(ae); // and store the format of the cell. topicCellFormats.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName()))); // goto next cell. cursor.goRight((short) 1, false); } /* * now - in which cell is every fillin? */ numCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_NUMBER)); topicCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_TOPIC)); responsibleCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_RESPONSIBLE)); timeCell = topicCells.indexOf(topicItems.get(FILLIN_TOPIC_TIME)); /* now that we know how the topics look like, * we get the format of the first and last rows. */ // format of first row cursor.gotoStart(false); do { firstRowFormat.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName()))); cursor.goRight((short) 1, false); } while (!cursor.getRangeName().startsWith("A")); // format of the last row cursor.gotoEnd(false); while (!cursor.getRangeName().startsWith("A")) { lastRowFormat.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName()))); cursor.goLeft((short) 1, false); } // we missed the A cell - so we have to add it also.. lastRowFormat.add(new TableCellFormatter(table.getCellByName(cursor.getRangeName()))); removeTableRows(table, 1 + rowsPerTopic, rows - rowsPerTopic - 1); } /** * @param topic the topic number to write * @param data the data of the topic. * @return the number of rows that have been added * to the table. 0 or a negative number: no rows added. */ private int write2(int topic, PropertyValue[] data) throws Exception { while (topic >= writtenTopics.size()) { writtenTopics.add(null); } writtenTopics.set(topic, PropertyNames.EMPTY_STRING); // make sure threr are enough rows for me... int rows = getRowCount(table); int reqRows = 1 + (topic + 1) * rowsPerTopic; int firstRow = reqRows - rowsPerTopic + 1; int diff = reqRows - rows; if (diff > 0) { insertTableRows(table, rows, diff); // set the item's text... } setItemText(numCell, data[0].Value); setItemText(topicCell, data[1].Value); setItemText(responsibleCell, data[2].Value); setItemText(timeCell, data[3].Value); // now write ! XTextTableCursor cursor = table.createCursorByCellName("A" + firstRow); for (int i = 0; i < topicCells.size(); i++) { ((AgendaElement) topicCells.get(i)).write(table.getCellByName(cursor.getRangeName())); cursor.goRight((short) 1, false); } // now format ! cursor.gotoCellByName("A" + firstRow, false); formatTable(cursor, topicCellFormats, false); return diff; } /** * check if the topic with the given index is written to the table. * @param topic the topic number (0 base) * @return true if the topic is already written to the table. False if not. * (false would mean new rows must be added to the table in order to * be able to write this topic). */ private boolean isWritten(int topic) { return (writtenTopics.size() > topic && writtenTopics.get(topic) != null); } /** * rewrites a single cell containing. * This is used in order to refresh the topic/responsible/duration data in the * preview document, in response to a change in the gui (by the user). * Since the structure of the topics table is flexible, we don't reference a cell * number. Rather, we use "what" argument to specify which cell should be redrawn. * The Topics object, which analyzed the structure of the topics table upon * initialization, refreshes the appropriate cell. * @param topic index of the topic (0 based). * @param what 0 for num, 1 for topic, 2 for responsible, 3 for duration * @param data the row's data. * @throws Exception if something goes wrong (though nothing should) */ public void writeCell(int topic, int what, PropertyValue[] data) throws Exception { // if the whole row should be written... if (!isWritten(topic)) { write(topic, data); // write only the "what" cell. } else { // calculate the table row. int firstRow = 1 + (topic * rowsPerTopic) + 1; // go to the first cell of this topic. XTextTableCursor cursor = table.createCursorByCellName("A" + firstRow); TextElement te = null; int cursorMoves = 0; switch (what) { case 0: te = setItemText(numCell, data[0].Value); cursorMoves = numCell; break; case 1: te = setItemText(topicCell, data[1].Value); cursorMoves = topicCell; break; case 2: te = setItemText(responsibleCell, data[2].Value); cursorMoves = responsibleCell; break; case 3: te = setItemText(timeCell, data[3].Value); cursorMoves = timeCell; break; } // move the cursor to the needed cell... if ( te != null) { cursor.goRight((short) cursorMoves, false); XCell xc = table.getCellByName(cursor.getRangeName()); // and write it ! te.write(xc); ((TableCellFormatter) topicCellFormats.get(cursorMoves)).format(xc); } } } /** * writes the given topic. * if the first topic was involved, reformat the * first row. * If any rows were added to the table, reformat * the last row. * @param topic the index of the topic to write. * @param data the topic's data. (see TopicsControl * for explanation about the topics data model) * @throws Exception if something goes wrong (though nothing should). */ public void write(int topic, PropertyValue[] data) throws Exception { int diff = write2(topic, data); /* if the first topic has been written, * one needs to reformat the first row. */ if (topic == 0) { formatFirstRow(); } /* * if any rows were added, one needs to format * the whole table again. */ if (diff > 0) { formatLastRow(); } } /** * Writes all the topics to thetopics table. * @param topicsData a List containing all Topic's Data. */ public void writeAll(List topicsData) { try { for (int i = 0; i < topicsData.size() - 1; i++) { write2(i, (PropertyValue[]) topicsData.get(i)); } formatLastRow(); } catch (Exception ex) { ex.printStackTrace(); } } /** * removes obsolete rows, reducing the * topics table to the given number of topics. * Note this method does only reducing - if * the number of topics given is greater than the * number of actual topics it does *not* add * new rows ! * Note also that the first topic will never be removed. * If the table contains no topics, the whole section will * be removed upon finishing. * The reason for that is a "table-design" one: the first topic is * maintained in order to be able to add rows with a design of this topic, * and not of the header row. * @param topics the number of topics the table should contain. * @throws Exception */ public void reduceDocumentTo(int topics) throws Exception { // we never remove the first topic... if (topics <= 0) { topics = 1; } XTableRows tableRows = table.getRows(); int targetNumOfRows = topics * rowsPerTopic + 1; if (tableRows.getCount() > targetNumOfRows) { tableRows.removeByIndex(targetNumOfRows, tableRows.getCount() - targetNumOfRows); } formatLastRow(); while (writtenTopics.size() > topics) { writtenTopics.remove(topics); } } /** * reapply the format of the first (header) row. */ private void formatFirstRow() { XTextTableCursor cursor = table.createCursorByCellName("A1"); formatTable(cursor, firstRowFormat, false); } /** * reapply the format of the last row. */ private void formatLastRow() { XTextTableCursor cursor = table.createCursorByCellName("A1"); cursor.gotoEnd(false); formatTable(cursor, lastRowFormat, true); } /** * returns a text element for the given cell, * which will write the given text. * @param cell the topics cell number. * @param value the value to write. * @return a TextElement object which will write the given value * to the given cell. */ private TextElement setItemText(int cell, Object value) { if (cell >= 0) { TextElement te = ((TextElement) topicCells.get(cell)); if (te != null) { te.text = value.toString(); } return te; } return null; } /** * formats a series of cells from the given one, * using the given List of TableCellFormatter objects, * in the given order. * This method is used to format the first (header) and the last * rows of the table. * @param cursor a table cursor, pointing to the start cell to format * @param formats a List containing TableCellFormatter objects. Each will format one cell in the direction specified. * @param reverse if true the cursor will move left, formatting in reverse order (used for the last row). */ private void formatTable(XTextTableCursor cursor, List formats, boolean reverse) { for (int i = 0; i < formats.size(); i++) { ((TableCellFormatter) formats.get(i)).format(table.getCellByName(cursor.getRangeName())); if (reverse) { cursor.goLeft((short) 1, false); } else { cursor.goRight((short) 1, false); } } } } /* * ================================= * Here are some static help methods * ================================= */ public static String[] getNamesWhichStartWith(String[] allNames, String prefix) { ArrayList v = new ArrayList(); for (int i = 0; i < allNames.length; i++) { if (allNames[i].startsWith(prefix)) { v.add(allNames[i]); } } String[] s = new String[v.size()]; return v.toArray(s); } /** * Convenience method, costs the given object to an XNamed, and returns its name. * @param obj an XNamed object. * @return the name of the given object. */ public static String getName(Object obj) { return UnoRuntime.queryInterface(XNamed.class, obj).getName(); } /** * convenience method, for removing a number of cells from a table. * @param table * @param start * @param count */ public static void removeTableRows(Object table, int start, int count) { XTableRows rows = UnoRuntime.queryInterface(XTextTable.class, table).getRows(); rows.removeByIndex(start, count); } /** * Convenience method for inserting some cells into a table. * @param table * @param start * @param count */ public static void insertTableRows(Object table, int start, int count) { XTableRows rows = UnoRuntime.queryInterface(XTextTable.class, table).getRows(); rows.insertByIndex(start, count); } /** * returns the row index for this cursor, assuming * the cursor points to a single cell. * @param cursor * @return the row index in which the cursor is. */ public static int getRowIndex(XTextTableCursor cursor) { return getRowIndex(cursor.getRangeName()); } /** * returns the row index for this cell name. * @param cellName * @return the row index for this cell name. */ public static int getRowIndex(String cellName) { return Integer.parseInt(cellName.substring(1)); } /** * returns the rows count of this table, assuming * there is no vertical merged cells. * @param table * @return the rows count of the given table. */ public static int getRowCount(XTextTable table) { String[] cells = table.getCellNames(); return getRowIndex(cells[cells.length - 1]); } } /* * =========================================================================================== * * End of AgendaTemplate class * * =========================================================================================== * */ /* * ================================= * The AgendaElement interface * ================================= */ /** * Interface that is used for writing content to a Uno Text / TextRange * @author rp143992 * */ interface AgendaElement { void write(Object any) throws Exception; } /* * ================================= * The ParaStyled class * ================================= */ /** * Basic implementation of the AgendaElement interface - * writes nothing, but applies a ParaStyle to the given XText/XTextRange * @author rp143992 * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ class ParaStyled implements AgendaElement { String paraStyle; ParaStyled(String paraStyle_) { paraStyle = paraStyle_; } void format(Object textRange) { XText o; o = UnoRuntime.queryInterface(XText.class, textRange); if (o == null) { o = UnoRuntime.queryInterface(XTextRange.class, textRange).getText(); } XTextRange xtr = UnoRuntime.queryInterface(XTextRange.class, textRange); XTextCursor cursor = o.createTextCursorByRange(xtr); Helper.setUnoPropertyValue(cursor, "ParaStyleName", paraStyle); } public void write(Object textRange) { format(textRange); } } /* * ================================= * The TextElement class * ================================= */ /** * A basic implementation of AgendaElement: * writes a String to the given XText/XTextRange, and applies * a ParaStyle to it (using the parent class). * @author rp143992 */ class TextElement extends ParaStyled { String text; TextElement(XTextRange range) { this(range.getString(), (String) Helper.getUnoPropertyValue(range.getStart(), "ParaStyleName")); } TextElement(String text_, String paraStyle_) { super(paraStyle_); text = text_; } public void write(Object textRange) { UnoRuntime.queryInterface(XTextRange.class, textRange).setString(text); if (!text.equals(PropertyNames.EMPTY_STRING)) { super.write(textRange); } } } /** * A Text element which, if the text to write is empty (null or PropertyNames.EMPTY_STRING) * inserts a placeholder instead. * @author rp143992 * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ class PlaceholderTextElement extends TextElement { String hint; String placeHolderText; XMultiServiceFactory xmsf; PlaceholderTextElement(XTextRange textRange, String placeHolderText_, String hint_, XMultiServiceFactory xmsf_) { super(textRange); placeHolderText = placeHolderText_; hint = hint_; xmsf = xmsf_; } PlaceholderTextElement(String text, String paraStyle, String placeHolderText_, String hint_, XMultiServiceFactory xmsf_) { super(text, paraStyle); placeHolderText = placeHolderText_; hint = hint_; xmsf = xmsf_; } public void write(Object textRange) { super.write(textRange); if (text == null || text.equals(PropertyNames.EMPTY_STRING)) { XTextRange xTextRange = UnoRuntime.queryInterface(XTextRange.class, textRange); try { XTextContent xTextContent = AgendaTemplate.createPlaceHolder(xmsf, placeHolderText, hint); xTextRange.getText().insertTextContent(xTextRange.getStart(), xTextContent, true); } catch (Exception ex) { ex.printStackTrace(); } } } } /* * ================================= * The PlaceHolder class * ================================= */ /** * An Agenda element which writes no text, but inserts a placeholder, and formats * it using a ParaStyleName. * @author rp143992 * */ class PlaceholderElement extends ParaStyled { String hint; String placeHolderText; XMultiServiceFactory xmsf; PlaceholderElement(String paraStyle, String placeHolderText_, String hint_, XMultiServiceFactory xmsf_) { super(paraStyle); placeHolderText = placeHolderText_; hint = hint_; xmsf = xmsf_; } public void write(Object textRange) { XTextRange xTextRange = UnoRuntime.queryInterface(XTextRange.class, textRange); try { XTextContent xTextContent = AgendaTemplate.createPlaceHolder(xmsf, placeHolderText, hint); xTextRange.getText().insertTextContent(xTextRange.getStart(), xTextContent, true); super.write(textRange); } catch (Exception ex) { ex.printStackTrace(); } } } /* * ================================= * The AgendaItem class * ================================= */ /** * An implementation of AgendaElement which * gets as a parameter a table cursor, and writes * a text to the cell marked by this table cursor, and * a place holder to the next cell. * @author rp143992 * * TODO To change the template for this generated type comment go to * Window - Preferences - Java - Code Style - Code Templates */ class AgendaItem implements AgendaElement { TextElement textElement; AgendaElement field; public Object table; String name; AgendaItem(String name_, TextElement te, AgendaElement f) { name = name_; field = f; textElement = te; } public void write(Object tableCursor) throws Exception { XTextTableCursor xTextTableCursor = UnoRuntime.queryInterface(XTextTableCursor.class, tableCursor); XTextTable xTextTable = UnoRuntime.queryInterface(XTextTable.class, table); String cellname = xTextTableCursor.getRangeName(); Object cell = xTextTable.getCellByName(cellname); textElement.write(cell); xTextTableCursor.goRight((short) 1, false); //second field is actually always null... // this is a preparation for adding placeholders. if (field != null) { field.write(xTextTable.getCellByName(xTextTableCursor.getRangeName())); } } } /* * ================================= * The TableCellFormatter class * ================================= */ /** * reads/write a table cell format from/to a table cell or a group of cells. * */ class TableCellFormatter { static String[] properties = new String[] { "BackColor", "BackTransparent", "BorderDistance", "BottomBorder", "BottomBorderDistance", "LeftBorder", "LeftBorderDistance", "RightBorder", "RightBorderDistance", "TopBorder", "TopBorderDistance" }; private Object[] values = new Object[properties.length]; public TableCellFormatter(Object tableCell) { for (int i = 0; i < properties.length; i++) { values[i] = Helper.getUnoPropertyValue(tableCell, properties[i]); } } public void format(Object tableCell) { Helper.setUnoPropertyValues(tableCell, properties, values); } }