/* Copyright 2011 Toby D. Rule
This file is part of CompPad, an OpenOffice extension to provide live
mathematical and engineering calculations within a Writer document.
CompPad 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 3 of the License, or
(at your option) any later version.
CompPad 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 CompPad. If not, see <http://www.gnu.org/licenses/>.
*/
package com.CompPad.OOO;
import com.CompPad.model.Document;
import com.CompPad.model.Listener;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.XEnumeration;
import com.sun.star.container.XEnumerationAccess;
import com.sun.star.container.XIndexAccess;
import com.sun.star.container.XNameAccess;
import com.sun.star.document.XEmbeddedObjectSupplier;
import com.sun.star.frame.XModel;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.text.XDependentTextField;
import com.sun.star.text.XTextDocument;
import com.sun.star.text.XTextEmbeddedObjectsSupplier;
import com.sun.star.text.XTextField;
import com.sun.star.text.XTextFieldsSupplier;
import com.sun.star.uno.Any;
import com.sun.star.uno.Exception;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.XRefreshable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Manage openoffice elements of the comppad document separately from the
* CompPad elements,
* @author brenda
*/
public class OOODocument {
private Integer nResult;
private Integer nError;
/* Case in CLSID strings is inconsistent - do we need to use
* case-insensitive comparison just to be safe? */
public static String CLSID_FORMULA="078B7ABA-54FC-457F-8551-6147e776a997";
public static String CLSID_CHART="12DCAE26-281F-416F-a234-c3086127382e";
private XTextDocument xTextDocument;
// Document compPadDocument;
/* This is a list of CompPad element in OOO Document */
private LinkedList<OOOElement> OOOElementList = new LinkedList();
private XMultiServiceFactory xMSF;
private Document document;
/* Create single listener to listen to many expressions. Must be inner class
in order to access private document fields and methods. */
public LinkedList <OOOElement>elementTypes = new LinkedList();
Hashtable<String, ArrayList<XEmbeddedObjectSupplier>> embeddedObjectHashtable = new Hashtable();
public OOODocument(XTextDocument xTextDocArg) throws java.lang.Exception{
Logger.getLogger("com.CompPad").log(Level.FINE,"Constructing OOODocument, \n xTextDocArg = "
+ xTextDocArg);
elementTypes.add(new OOOError());
elementTypes.add(new OOOPlot());
elementTypes.add(new OOOResult());
// Get xMultiServiceFactory interface for document
/* initialize the CompPad Document object */
nResult=0;
nError=0;
document = new Document();
/* get document url, and if it's file:// then strip this and the file name and
* set that as the document working directory */
String docURL = ((XModel)UnoRuntime.queryInterface(XModel.class, xTextDocArg)).getURL();
if (docURL.contains("file://")){
String wdir = docURL = docURL.replaceFirst("file://", "").replaceFirst("/[^/]+$", "") + "/";
document.setWorkingDir(wdir);
}
xTextDocument = xTextDocArg;
xMSF = (XMultiServiceFactory) UnoRuntime.queryInterface(
XMultiServiceFactory.class, xTextDocument);
// Constructor will find all the existing formulas and add them
// to the object.
/* Locate formulas in document and create corresponding OOOExpression
* object for each one. */
/* We need to index OOOElements according to the identity
of the openoffice objects! Otherwise, we will create new OOOExpression
objects that are duplicates. */
/* Find text fields and link to formulas */
/* It shouldn't do getEmbeddedObjects twice! This is slow */
Logger.getLogger("com.CompPad").log(Level.FINE,"Finding embedded objects");
ArrayList<XEmbeddedObjectSupplier> eos;
Logger.getLogger("com.CompPad").fine("created eos");
eos = getEmbeddedObjects(CLSID_FORMULA);
Logger.getLogger("com.CompPad").fine("got eos");
for (XEmbeddedObjectSupplier xEO:eos){
// Add expression to expression list
/* NEED TO CHANGE SO ELEMENTS ARE INDEXED BY THEIR OPENOFFICE
* OBJECTS */
Logger.getLogger("com.CompPad").log(Level.FINE," Adding OOOExpression to OOOElementList");
OOOElementList.add(new OOOExpression(this,xEO));
}
for (XEmbeddedObjectSupplier xEO:getEmbeddedObjects(CLSID_CHART)){
System.err.println(" Adding OOOPlot to OOOElementList");
OOOElementList.add(new OOOPlot(this,xEO));
}
for (XDependentTextField xDTF : getTextFields()){
String name = (String) xDTF.getTextFieldMaster().getPropertyValue("Name");
/* NEED TO INDEX BY THE OOO Object!! */
if (name.startsWith(" OOOResult")){
/* create OOOResult object and add to OOOElementList*/
OOOElementList.add(new OOOResult(this,xDTF));
}
else if (name.startsWith(" OOOError")){
/* Create OOOError object and add to OOOElementList */
OOOElementList.add(new OOOError(this,xDTF));
}
}
/* Sort OOOExpressions so their order in expressionList corresponds to order
* in document. This is needed in FixTextFields */
/* This will throw an error if OOOElements is empty */
Collections.sort(OOOElementList);
/* Now link OOOError and OOOResult elements to OOOExpressions according
* to their order in the document. When expressions are evaluated,
* these may be later created or deleted, according to results of listener.
* If expression says "no result" then the result will be deleted. If
* expression says "result" then result will be created or updated.
* If expression says "error" then error will be created or updated.
* */
OOOExpression currentExpression=null;
for (OOOElement oooElement:OOOElementList){
if (OOOExpression.class.isInstance(oooElement)){
/* Set this as the "current" expression, and link subsequent results or
* errors or plots or whatever to this expression. */
currentExpression=(OOOExpression)oooElement;
}
else{
/* Will throw error if null */
/* Only last dependant element of each class gets kept */
currentExpression.setDependent(oooElement);
}
}
/* At this point, everythings been added to the document, and the
* OOOExpression objects should have taken care of creating the
* expressions.
*
* We will pass a comparable interface from the OOOExpression to the
* expression so that the expressions can sort themselves.
*/
/* Fix text fields that no longer have matching expression, or that
* are not positioned correctly */
/* I would rather not do this until I actually evaluate the expression */
fixTextFields();
/* */
/* Add refresh listener */
System.out.println("Adding refresh listener");
document.setRefreshListener(new RefreshListener());
/* Evaluate document */
System.out.println("Evaluating document");
document.evaluate();
}
public XTextDocument getTextDocument(){
// Returns the XTextDocument interface for the document containing compPAd
return xTextDocument;
}
/**
* get list of XEmbeddedObjectSuppliers for embedded math objects
* @return
* @throws java.lang.Exception
*/
private ArrayList<XEmbeddedObjectSupplier>
getEmbeddedObjects(String argCLSID) throws java.lang.Exception {
ArrayList<XEmbeddedObjectSupplier> xEmbeddedObs= new ArrayList();
Logger.getLogger("com.CompPad").fine("getEmbeddedObjects 00");
/* !!!! It would be better to make a hashtable with CLSID as key,
* and with the members being linked lists filled with the XEOS's !!!! */
/* Test enumeration access - should be faster if it works ! */
/* For now, check if hashtable is zero length. In future, may want to
* re-check hashtable if new objects have been added. This is a slow
* loop, so only do it once */
if (embeddedObjectHashtable.size()==0){
Logger.getLogger("com.CompPad").fine("getEmbeddedObjects 02");
XEmbeddedObjectSupplier xEOS;
XTextEmbeddedObjectsSupplier xEOsS =
(XTextEmbeddedObjectsSupplier) UnoRuntime.queryInterface(
com.sun.star.text.XTextEmbeddedObjectsSupplier.class,
xTextDocument);
Logger.getLogger("com.CompPad").fine("getEmbeddedObjects 05");
XIndexAccess indexEmbeddedObjects =
(XIndexAccess) UnoRuntime.queryInterface(
XIndexAccess.class,(xEOsS.getEmbeddedObjects()));
Logger.getLogger("com.CompPad").fine("getEmbeddedObjects 10");
for (int i =0; i<indexEmbeddedObjects.getCount();i++){
Logger.getLogger("com.CompPad").fine("getEmbeddedObjects20");
// EmbeddedObjects.getByName() returns an Any object, which has the
// type embedded with the object.s
xEOS= (XEmbeddedObjectSupplier)
((Any) indexEmbeddedObjects.getByIndex(i)).getObject();
/* The following three lines appear to have been causing slowdown.
* And also, they are not needed! */
//Object xEO=xEOS.getEmbeddedObject();
//XPropertySet xPSxEO=(XPropertySet)
// UnoRuntime.queryInterface(XPropertySet.class, xEO);
// The service TextEmbeddedObject that provides the XEOS interface
// also provides the CLSID property
XPropertySet xPSxEOS=(XPropertySet)
UnoRuntime.queryInterface(XPropertySet.class,xEOS);
// Get CLSID value
String clsid=(String) xPSxEOS.getPropertyValue("CLSID");
if (!embeddedObjectHashtable.containsKey(clsid)){
embeddedObjectHashtable.put(clsid, new ArrayList<XEmbeddedObjectSupplier>());
}
embeddedObjectHashtable.get(clsid).add(xEOS);
}
}
Logger.getLogger("com.CompPad").fine("getEmbeddedObjects 30");
/* Return empty list rather than null */
if (!embeddedObjectHashtable.containsKey(argCLSID)){
return new ArrayList<XEmbeddedObjectSupplier>();
}
else{
return embeddedObjectHashtable.get(argCLSID);
}
}
/**
* Find text fields, create OOOResult or OOOError, and add to list of
* OOOElements. Don't worry now if there are field masters with multiple
* dependent fields.
*/
private void findTextFields () throws java.lang.Exception {
XTextFieldsSupplier xTFS = (XTextFieldsSupplier)
UnoRuntime.queryInterface(XTextFieldsSupplier.class,xTextDocument);
List docFieldMasterNames =
Arrays.asList((xTFS.getTextFieldMasters()).getElementNames());
/* Create either OOOREsult or OOOError and add to list of OOOElements */
XEnumeration xEnum = xTFS.getTextFields().createEnumeration();
while (xEnum.hasMoreElements()){
XDependentTextField xTF = (XDependentTextField)xEnum.nextElement();
String name = (String) xTF.getTextFieldMaster().getPropertyValue("Name");
/* NEED TO INDEX BY THE OOO Object */
if (name.startsWith("com.sun.star.text.fieldmaster.User.OOOResult")){
/* create OOOResult object and add to OOOElementList*/
OOOElementList.add(new OOOResult(this,xTF));
}
else if (name.startsWith("com.sun.star.text.fieldmaster.User.OOOError")){
/* Create OOOError object and add to OOOElementList */
OOOElementList.add(new OOOError(this,xTF));
}
}
}
private ArrayList<XDependentTextField> getTextFields () throws java.lang.Exception {
XTextFieldsSupplier xTFS = (XTextFieldsSupplier)
UnoRuntime.queryInterface(XTextFieldsSupplier.class,xTextDocument);
ArrayList<XDependentTextField> xDTF=new ArrayList();
/* Create either OOOREsult or OOOError and add to list of OOOElements */
XEnumeration xEnum = xTFS.getTextFields().createEnumeration();
XTextField xTF;
while (xEnum.hasMoreElements()){
// xDTF.add( (XDependentTextField) xEnum.nextElement())) ;
// Logger.getLogger("com.CompPad").log(Level.FINE,"nextelement: "+((XTextField)((Any)xEnum.nextElement()).getObject()).getPresentation(true));
xTF= (XTextField)((Any)xEnum.nextElement()).getObject();
xDTF.add((XDependentTextField)UnoRuntime.queryInterface(
XDependentTextField.class,xTF));
}
return xDTF;
}
private void fixTextFields() throws java.lang.Exception {
Logger.getLogger("com.CompPad").log(Level.FINE,"Fixing Text Fields");
String name;
XPropertySet fieldMaster;
LinkedList expFieldMasterNames=new LinkedList();
XTextFieldsSupplier xTFS = (XTextFieldsSupplier)
UnoRuntime.queryInterface(XTextFieldsSupplier.class,xTextDocument);
List docFieldMasterNames =
Arrays.asList((xTFS.getTextFieldMasters()).getElementNames());
String expName;
for (Object item:docFieldMasterNames){
name = (String)item;
/* The strings in element list don't contain the prefix, so
prefix must be removed from docFieldMasterName before checking it */
expName=name.replace("com.sun.star.text.fieldmaster.User.", "");
if (name.startsWith("com.sun.star.text.fieldmaster.User.OOO") & !this.hasElementName(expName)){
Logger.getLogger("com.CompPad").log(Level.FINE," orphan:"+ expName);
/* the field master is orphaned. Remove the field master and
* dependent fields */
fieldMaster = (XPropertySet)(((Any)
((xTFS.getTextFieldMasters()).getByName(name))).getObject());
/* Dispose of fieldmaster */
((XComponent)UnoRuntime.queryInterface(
XComponent.class, fieldMaster)).dispose();
/* When the fieldmaster is disposed of, the
* dependent text fields are disposed of as well, since they
* can't exist without a master */
}
}
}
/**
* get the associated comppad document
* @return
*/
Document getCompPadDocument(){
return document;
}
public XPropertySet newFieldMaster(String name) throws java.lang.Exception{
XPropertySet outputFieldMaster;
Object oFM;
Logger.getLogger("com.CompPad").log(Level.FINE,"newFieldMaster: "+name);
try{
/* Should work in OO 3 */
oFM = (Object) xMSF.createInstance("com.sun.star.text.fieldmaster.User");
}
catch(com.sun.star.uno.Exception exception){
/* Should work in OO 2 */
oFM = (Object) xMSF.createInstance("com.sun.star.text.FieldMaster.User");
}
// There is not field master interface, just a bunch of properties
outputFieldMaster = (XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, oFM);
outputFieldMaster.setPropertyValue("Name", name);
/* I think I had to change this for OpenOffice 2.0. I expect
* I'll have to check the type of the object before accessing it. */
// Logger.getLogger("com.CompPad").log(Level.FINE,"Created OutputFieldMaster "
// + (String)((Any)(outputFieldMaster.getPropertyValue("Name"))).getObject());
Logger.getLogger("com.CompPad").log(Level.FINE,"Created OutputFieldMaster "
+ outputFieldMaster.getPropertyValue("Name"));
return outputFieldMaster;
}
public XDependentTextField createField(XPropertySet outputFieldMaster) throws Exception{
/* Create fields, since they either didn't exist, or were removed */
/* Found online that these may be mis-spelled in openoffice 2 ! */
Object oTF;
try{
/* This should work in OO 3 */
oTF = (Object) xMSF.createInstance("com.sun.star.text.textfield.User");
}
catch(com.sun.star.uno.Exception exception){
/* this should work in OO 2 */
oTF = (Object) xMSF.createInstance("com.sun.star.text.TextField.User");
}
XDependentTextField xTextField = (XDependentTextField)
UnoRuntime.queryInterface(XDependentTextField.class, oTF);
/* Attach to fieldmaster */
xTextField.attachTextFieldMaster(outputFieldMaster);
return xTextField;
}
/* not used?? */
public XPropertySet findFieldMaster(String name) throws Exception {
// See if there is a user field master called "CompPad"+name
XPropertySet outputFieldMaster;
XTextFieldsSupplier xTFS = (XTextFieldsSupplier)
UnoRuntime.queryInterface(XTextFieldsSupplier.class, xTextDocument);
XNameAccess xTFMs = xTFS.getTextFieldMasters();
try {
Logger.getLogger("com.CompPad").log(Level.FINE,"Getting fieldmaster for com.sun.star.text.fieldmaster.User.CompPad" + name);
// OO 2 mis-spells fieldmaster as FieldMaster
try{
/* Should work in OO 3 */
outputFieldMaster = (XPropertySet) ((Any) xTFMs.getByName(
"com.sun.star.text.fieldmaster.User.CompPad" + name)).getObject();
Logger.getLogger("com.CompPad").log(Level.FINE,"Field exists.");
return outputFieldMaster;
}
/* Should work in OO 2 */
catch (com.sun.star.uno.Exception exception){
outputFieldMaster = (XPropertySet) ((Any) xTFMs.getByName(
"com.sun.star.text.FieldMaster.User.CompPad" + name)).getObject();
Logger.getLogger("com.CompPad").log(Level.FINE,"Field exists.");
return outputFieldMaster;
}
} catch (com.sun.star.container.NoSuchElementException e) {
Logger.getLogger("com.CompPad").log(Level.FINE,"Unable to get field master.");
return null;
}
}
/* Maybe we should create an expression listener inner class and
* send it to all the expressions, so that they can tell the document
* to add a new error, plot, whatever.
*/
public String newResultName() throws java.lang.Exception {
/* Provide a unique result name */
String errorName = "OOOResult"+nResult;
while (this.hasElementName(errorName)){
nResult=nResult+1;
errorName="OOOResult"+nResult;
}
nResult=nResult+1;
return errorName;
}
public String newErrorName() throws java.lang.Exception {
/* Provide a unique result name */
String errorName = "OOOError"+nError;
while (this.hasElementName(errorName)){
nError=nError+1;
errorName="OOOError"+nError;
}
nError=nError+1;
return errorName;
}
private boolean hasElementName(String elementName) throws java.lang.Exception {
boolean retval = false;
for (OOOElement element: OOOElementList) {
if (elementName.equals(element.getName())){
retval=true;
break;
}
}
return retval;
}
private void removeElement(OOOElement e){
}
private class RefreshListener implements Listener{
public void refresh() {
System.out.println("Refreshing Document");
/* Refresh all text fields */
XTextFieldsSupplier xTFS = (XTextFieldsSupplier)
UnoRuntime.queryInterface(XTextFieldsSupplier.class, xTextDocument);
XEnumerationAccess xEA = xTFS.getTextFields();
/* Get XRefreshable interface */
XRefreshable xRef= (XRefreshable)
UnoRuntime.queryInterface(XRefreshable.class,xEA);
/* Refresh fields */
xRef.refresh();
}
public void setValue(Object o) {
throw new UnsupportedOperationException("Not supported yet.");
}
public Object getValue(){
throw new UnsupportedOperationException("Not supported yet.");
}
}
}