diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /forms/qa/integration | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
31 files changed, 5950 insertions, 0 deletions
diff --git a/forms/qa/integration/forms/BooleanValidator.java b/forms/qa/integration/forms/BooleanValidator.java new file mode 100644 index 000000000..9d85a8616 --- /dev/null +++ b/forms/qa/integration/forms/BooleanValidator.java @@ -0,0 +1,64 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.AnyConverter; + +public class BooleanValidator extends integration.forms.ControlValidator +{ + private final boolean m_preventChecked; + + /** Creates a new instance of BooleanValidator */ + public BooleanValidator( boolean preventChecked ) + { + m_preventChecked = preventChecked; + } + + public String explainInvalid( Object Value ) + { + try + { + if ( AnyConverter.isVoid( Value ) ) + return "'indetermined' is not an allowed state"; + boolean value = ((Boolean)Value).booleanValue(); + if ( m_preventChecked && ( value ) ) + return "no no no. Don't check it."; + } + catch( java.lang.Exception e ) + { + return "ooops. Unknown error"; + } + return ""; + } + + public boolean isValid( Object Value ) + { + try + { + if ( AnyConverter.isVoid( Value ) ) + return false; + + boolean value = ((Boolean)Value).booleanValue(); + return !(m_preventChecked && ( value )); + } + catch( java.lang.Exception e ) + { + } + return false; + } +} diff --git a/forms/qa/integration/forms/CellBinding.java b/forms/qa/integration/forms/CellBinding.java new file mode 100644 index 000000000..56d811a25 --- /dev/null +++ b/forms/qa/integration/forms/CellBinding.java @@ -0,0 +1,543 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.util.*; +import com.sun.star.lang.*; +import com.sun.star.beans.*; +import com.sun.star.form.binding.*; +import com.sun.star.accessibility.*; +import com.sun.star.awt.XListBox; +import com.sun.star.table.CellAddress; +import com.sun.star.table.XCell; +import com.sun.star.sheet.XCellRangeData; +import com.sun.star.sheet.XCellRangeFormula; +import com.sun.star.text.XTextRange; + +public class CellBinding extends complexlib.ComplexTestCase +{ + /** the test document our form layer lives in */ + private SpreadsheetDocument m_document; + /** our form layer */ + private FormLayer m_formLayer; + + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkTextFieldBinding", + "checkBooleanRadioBinding", + "checkStringRadioBinding", + "checkBooleanCheckBoxBinding", + "checkStringCheckBoxBinding", + "checkListBoxBinding", + "checkListBoxIndexBinding" + }; + } + + @Override + public String getTestObjectName() + { + return "Form Control Spreadsheet Cell Binding Test"; + } + + /* ------------------------------------------------------------------ */ + /** closes our document, if we have an open one + */ + private void closeDocument() + { + try + { + // close our document + if ( m_document != null ) + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, + m_document.getDocument() ); + closeDoc.close( true ); + } + } + catch ( com.sun.star.uno.Exception e ) + { + } + } + + /* ------------------------------------------------------------------ */ + public void before() throws com.sun.star.uno.Exception, java.lang.Exception + { + /* our service factory */ + XMultiServiceFactory orb = param.getMSF(); + m_document = new SpreadsheetDocument( orb ); + m_formLayer = new FormLayer( m_document ); + } + + /* ------------------------------------------------------------------ */ + public void after() throws java.lang.Exception + { + closeDocument(); + } + + /* ------------------------------------------------------------------ */ + public void checkTextFieldBinding( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + final short col = 0; + final short row = 2; + final String text = "content"; + final String otherText = "something else"; + final String yetAnotherText = "yet another text"; + + // create a normal text control + XPropertySet controlModel = m_formLayer.createControlAndShape( "DatabaseTextField", 30, 9, 30, 6 ); + + // bind it to cell A1 + bindToCell( controlModel, col, row ); + + // switch to alive mode + m_document.getCurrentView().toggleFormDesignMode(); + + // test the data transfer control -> cell + simulateUserTextInput( controlModel, text ); + verifyStringCellContent( col, row, text, "A text field does not forward its user input to the cell." ); + + // the same, but this time changing the control value programmatically + controlModel.setPropertyValue( "Text", otherText ); + verifyStringCellContent( col, row, otherText, "A text field does not forward programmatic changes to the cell." ); + + // the other way round: cell->control + setCellText( col, row, yetAnotherText ); + String controlText = (String)controlModel.getPropertyValue( "Text" ); + if ( !controlText.equals( yetAnotherText ) ) + failed( "Changes in the cell are not forwarded to the text field." ); + } + /* ------------------------------------------------------------------ */ + public void checkBooleanRadioBinding( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + // two radio buttons + XPropertySet primaryRadio = createRadio( 28, "radio button no. 1", "radio group", "primary" ); + XPropertySet secondaryRadio = createRadio( 33, "radio button no. 2", "radio group", "secodary" ); + + // bind them + short col = (short)0; + short row1 = (short)6; + short row2 = (short)7; + bindToCell( primaryRadio, col, row1 ); + bindToCell( secondaryRadio, col, row2 ); + + // check the first button + simulateUserRadioCheck( primaryRadio ); + // check the cell content + verifyNumericCellContent( col, row1, 1, "Radio buttons do not forward their (boolean) values to cells (1)." ); + verifyNumericCellContent( col, row2, 0, "Radio buttons do not forward their (boolean) values to cells (2)." ); + // check the second button + simulateUserRadioCheck( secondaryRadio ); + // check the cell content + verifyNumericCellContent( col, row1, 0, "Radio buttons do not forward their (boolean) values to cells (3)." ); + verifyNumericCellContent( col, row2, 1, "Radio buttons do not forward their (boolean) values to cells (4)." ); + + // the other way round: writing values into the cell + setCellValue( col, row1, 1.0 ); + // setting this should have checked the primary radio, which should have unchecked the secondary radio, + // which should have been propagated to the second cell + verifyNumericCellContent( col, row2, 0, "Changing primary cell is not propagated to the secondary cell (via the radio buttons)." ); + + // setting an empty cell should result in the radio being unchecked + setCellEmpty( col, row1 ); + if ( ((Short)primaryRadio.getPropertyValue( "State" )).shortValue() != 0 ) + failed( "Setting a cell to 'empty' does not reset the bound radio button." ); + } + + /* ------------------------------------------------------------------ */ + public void checkStringRadioBinding( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + // two radio buttons + XPropertySet primaryRadio = createRadio( 46, "radio button A", "radio ref group", "primary" ); + XPropertySet secondaryRadio = createRadio( 51, "radio button B", "radio ref group", "secodary" ); + + // give the ref values + String refValueA = "ref value A"; + String refValueB = "ref value B"; + primaryRadio.setPropertyValue( "RefValue", refValueA ); + secondaryRadio.setPropertyValue( "RefValue", refValueB ); + + // bind them to the same cell + short col = (short)0; + short row = (short)10; + bindToCell( primaryRadio, col, row ); + bindToCell( secondaryRadio, col, row ); + + // checking a radio should set the respective ref value at the cell + simulateUserRadioCheck( primaryRadio ); + verifyStringCellContent( col, row, refValueA, "A bound radio button with a reference value does not pass this value to the cell upon checking (1)." ); + simulateUserRadioCheck( secondaryRadio ); + verifyStringCellContent( col, row, refValueB, "A bound radio button with a reference value does not pass this value to the cell upon checking (2)." ); + + // changing the cell should check the buttons if the cell text equals the ref value + setCellText( col, row, "no ref value" ); + verifyRadioStates( primaryRadio, secondaryRadio, (short)0, (short)0, "Radio button not unchecked, though the bound cell value does not equal ref value." ); + + setCellText( col, row, refValueA ); + verifyRadioStates( primaryRadio, secondaryRadio, (short)1, (short)0, "Radio button not properly un/checked according to the cell and ref value (1)." ); + + setCellText( col, row, refValueB ); + verifyRadioStates( primaryRadio, secondaryRadio, (short)0, (short)1, "Radio button not properly un/checked according to the cell and ref value (2)." ); + } + + /* ------------------------------------------------------------------ */ + public void checkBooleanCheckBoxBinding( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet checkBox = m_formLayer.createControlAndShape( "DatabaseCheckBox", 30, 59, 40, 4 ); + checkBox.setPropertyValue( "Label", "check box" ); + checkBox.setPropertyValue( "TriState", Boolean.TRUE ); + + short col = (short)0; + short row = (short)13; + bindToCell( checkBox, col, row ); + + // initialize with "not checked" + checkBox.setPropertyValue( "State", Short.valueOf( (short)0 ) ); + verifyNumericCellContent( col, row, 0, "programmatically unchecking the check box is not propagated to the cell." ); + + // first click: "not checked" -> "checked" + simulateUserCheckBoxCheck( checkBox, (short)1 ); + verifyNumericCellContent( col, row, 1, "moving the check box state to 'checked' is not propagated to the cell." ); + + // second click: "checked" -> "indetermined" + simulateUserCheckBoxCheck( checkBox, (short)2 ); + verifyVoidCell( col, row, "propagating the 'indetermined' state to the cell does not work." ); + + // third click: "indetermined" -> "not checked" + simulateUserCheckBoxCheck( checkBox, (short)0 ); + verifyNumericCellContent( col, row, 0, "unchecking a check box via UI is not propagated to the cell." ); + } + + /* ------------------------------------------------------------------ */ + public void checkStringCheckBoxBinding( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + String refValue = "checked "; + + XPropertySet checkBox = m_formLayer.createControlAndShape( "DatabaseCheckBox", 30, 68, 40, 4 ); + checkBox.setPropertyValue( "Label", "check box with ref value" ); + checkBox.setPropertyValue( "TriState", Boolean.TRUE ); + checkBox.setPropertyValue( "RefValue", refValue ); + + short col = (short)0; + short row = (short)15; + bindToCell( checkBox, col, row ); + + // initialize with "not checked" + checkBox.setPropertyValue( "State", Short.valueOf( (short)0 ) ); + verifyNumericCellContent( col, row, 0, "programmatically unchecking the check box is not propagated to the cell." ); + + // first click: "not checked" -> "checked" + simulateUserCheckBoxCheck( checkBox, (short)1 ); + verifyStringCellContent( col, row, refValue, "moving the check box state to 'checked' does not propagated the ref value to the cell." ); + + // second click: "checked" -> "indetermined" + simulateUserCheckBoxCheck( checkBox, (short)2 ); + verifyVoidCell( col, row, "propagating the 'indetermined' state to the cell does not work, when exchanging ref values." ); + + // third click: "indetermined" -> "not checked" + simulateUserCheckBoxCheck( checkBox, (short)0 ); + verifyStringCellContent( col, row, "", "unchecking a check box via UI does not propagated the ref value to the cell." ); + } + + /* ------------------------------------------------------------------ */ + /** verifies that a list box, which is bound via an ordinary value binding, + * works as expected + */ + public void checkListBoxBinding( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet listBox = m_formLayer.createControlAndShape( "DatabaseListBox", 30, 80, 40, 6 ); + listBox.setPropertyValue( "Dropdown", Boolean.TRUE ); + listBox.setPropertyValue( "StringItemList", new String[] { "Apples", "Oranges", "Peaches" } ); + + short col = (short)0; + short row = (short)18; + + + // add a list entry source which fills the list boxes list from cells in the + // spreadsheet + short sourceCol = (short)4; + setCellText( sourceCol, (short)( row - 1 ), "Apples" ); + setCellText( sourceCol, (short)( row + 0 ), "Oranges" ); + setCellText( sourceCol, (short)( row + 1 ), "Peaches" ); + + // TODO: this is currently prone to deadlocks + + + // bind to a cell + bindToCell( listBox, col, row ); + + + // do the tests + listBox.setPropertyValue( "SelectedItems", new short[] { (short)0 } ); + verifyStringCellContent( col, row, "Apples", "programmatically selecting a list entry is not propagated to the cell." ); + + simulateUserListBoxSelection( listBox, "Oranges" ); + verifyStringCellContent( col, row, "Oranges", "UI-selecting a list entry is not propagated to the cell." ); + + setCellText( col, row, "Peaches" ); + short[] selectedItems = (short[])listBox.getPropertyValue( "SelectedItems" ); + assureEquals( "changes in the cell bound to a list box are not propagated to the list box selection", + 2, selectedItems[0] ); + } + + /* ------------------------------------------------------------------ */ + /** verifies that a list box, which is bound via a value binding exchanging the <b>index</b> + * of the selected entry, works as expected + */ + public void checkListBoxIndexBinding() throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet listBox = m_formLayer.createControlAndShape( "DatabaseListBox", 30, 94, 40, 6 ); + listBox.setPropertyValue( "Dropdown", Boolean.TRUE ); + listBox.setPropertyValue( "StringItemList", new String[] { "Pears", "Bananas", "Strawberries" } ); + + short col = (short)0; + short row = (short)21; + + + // add a list entry source which fills the list boxes list from cells in the + // spreadsheet + short sourceCol = (short)4; + setCellText( sourceCol, (short)( row - 1 ), "Pears" ); + setCellText( sourceCol, (short)( row + 0 ), "Bananas" ); + setCellText( sourceCol, (short)( row + 1 ), "Strawberries" ); + + // TODO: this is currently prone to deadlocks + + + // bind to a cell + bindToCell( listBox, col, row, "com.sun.star.table.ListPositionCellBinding" ); + + + // do the tests + listBox.setPropertyValue( "SelectedItems", new short[] { (short)0 } ); + verifyNumericCellContent( col, row, 1, "programmatically selecting a list entry is not propagated (as index) to the cell." ); + + simulateUserListBoxSelection( listBox, "Bananas" ); + verifyNumericCellContent( col, row, 2, "UI-selecting a list entry is not propagated (as index) to the cell." ); + + setCellValue( col, row, 3 ); + short[] selectedItems = (short[])listBox.getPropertyValue( "SelectedItems" ); + assureEquals( "changes in the cell bound to a list box via list index are not propagated to the list box selection", + 2, selectedItems[0] ); + } + + /* ------------------------------------------------------------------ */ + /** verifies that the content of a given cell equals a given string + */ + private XPropertySet createRadio( int yPos, String label, String name, String tag ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet radio = m_formLayer.createControlAndShape( "DatabaseRadioButton", 30, yPos, 40, 4 ); + radio.setPropertyValue( "Label", label ); + radio.setPropertyValue( "Name", name ); + radio.setPropertyValue( "Tag", tag ); + return radio; + } + + /* ------------------------------------------------------------------ */ + /** verifies the states of two radio button + */ + private boolean verifyRadioStates( XPropertySet radio1, XPropertySet radio2, short value1, short value2, + String errorMessage ) throws com.sun.star.uno.Exception, java.lang.Exception + { + if ( ( ((Short)radio1.getPropertyValue( "State" )).shortValue() != value1 ) + || ( ((Short)radio2.getPropertyValue( "State" )).shortValue() != value2 ) + ) + { + failed( errorMessage ); + return false; + } + return true; + } + + /* ------------------------------------------------------------------ */ + /** verifies that the content of a given cell equals a given string + */ + private boolean verifyVoidCell( short col, short row, String failErrorMessage ) throws com.sun.star.uno.Exception + { + XCellRangeData cell = UnoRuntime.queryInterface( XCellRangeData.class, + m_document.getSheet( 0 ).getCellByPosition( col, row ) + ); + Object cellContent = cell.getDataArray()[0][0]; + if ( ((com.sun.star.uno.Any)cellContent).getType().getTypeClass() != com.sun.star.uno.TypeClass.VOID ) + { + failed( failErrorMessage ); + return false; + } + return true; + } + + /* ------------------------------------------------------------------ */ + /** verifies that the content of a given cell equals a given string + */ + private boolean verifyNumericCellContent( short col, short row, double value, String failErrorMessage ) throws com.sun.star.uno.Exception + { + XCell cell = UnoRuntime.queryInterface( XCell.class, + m_document.getSheet( 0 ).getCellByPosition( col, row ) + ); + if ( cell.getValue() != value ) + { + failed( failErrorMessage ); + return false; + } + return true; + } + + /* ------------------------------------------------------------------ */ + /** verifies that the content of a given cell equals a given string + */ + private boolean verifyStringCellContent( short col, short row, String text, String failErrorMessage ) throws com.sun.star.uno.Exception + { + XTextRange cell = UnoRuntime.queryInterface( XTextRange.class, + m_document.getSheet( 0 ).getCellByPosition( col, row ) + ); + if ( !cell.getString().equals( text ) ) + { + failed( failErrorMessage ); + return false; + } + return true; + } + + /* ------------------------------------------------------------------ */ + /** sets the text of a given cell to a given string + */ + private void setCellText( short col, short row, String text ) throws com.sun.star.uno.Exception + { + XTextRange cell = UnoRuntime.queryInterface( XTextRange.class, + m_document.getSheet( 0 ).getCellByPosition( col, row ) + ); + cell.setString( text ); + } + + /* ------------------------------------------------------------------ */ + /** sets a numeric value in a given cell + */ + private void setCellValue( short col, short row, double value ) throws com.sun.star.uno.Exception + { + XCell cell = UnoRuntime.queryInterface( XCell.class, + m_document.getSheet( 0 ).getCellByPosition( col, row ) + ); + cell.setValue( value ); + } + + /* ------------------------------------------------------------------ */ + /** sets a numeric value in a given cell + */ + private void setCellEmpty( short col, short row ) throws com.sun.star.uno.Exception + { + // as long as #i29130# is not fixed, we do not set the cell to "empty", but to + // an invalid form, which serves well for our purpose + XCellRangeFormula cell = UnoRuntime.queryInterface( XCellRangeFormula.class, + m_document.getSheet( 0 ).getCellByPosition( col, row ) + ); + String[][] args = new String[][] { new String[] { "=INVALID_FUNCTION()" } }; + cell.setFormulaArray( args ); + } + + /* ------------------------------------------------------------------ */ + /** binds the given control model to the given cell in the first sheet, + * using the given service name for the binding + */ + private void bindToCell( XPropertySet controlModel, short column, short row, String _bindingServiceName ) throws com.sun.star.uno.Exception + { + XBindableValue bindableModel = UnoRuntime.queryInterface( XBindableValue.class, + controlModel + ); + + CellAddress address = new CellAddress(); + address.Column = column; + address.Row = row; + address.Sheet = 0; + + NamedValue[] parameters = new NamedValue[] { new NamedValue() }; + parameters[0].Name = "BoundCell"; + parameters[0].Value = address; + + XValueBinding cellBinding = UnoRuntime.queryInterface( XValueBinding.class, + m_document.createInstanceWithArguments( _bindingServiceName, parameters ) + ); + + bindableModel.setValueBinding( cellBinding ); + } + + /* ------------------------------------------------------------------ */ + /** binds the given control model to the given cell in the first sheet + */ + private void bindToCell( XPropertySet _controlModel, short _column, short _row ) throws com.sun.star.uno.Exception + { + bindToCell( _controlModel, _column, _row, "com.sun.star.table.CellValueBinding" ); + } + + /* ------------------------------------------------------------------ */ + /** simulates a user action to check a radio button + */ + private void simulateUserRadioCheck( XPropertySet radioModel ) throws com.sun.star.uno.Exception + { + XAccessible accessible = UnoRuntime.queryInterface( + XAccessible.class, m_document.getCurrentView().getControl( radioModel ) ); + + XAccessibleValue xValue = UnoRuntime.queryInterface( + XAccessibleValue.class, accessible.getAccessibleContext() ); + + Integer newValue = Integer.valueOf( 1 ); + xValue.setCurrentValue( newValue ); + } + + /* ------------------------------------------------------------------ */ + /** simulates a user action to check a radio button + */ + private void simulateUserCheckBoxCheck( XPropertySet checkBox, short state ) throws com.sun.star.uno.Exception + { + XAccessible accessible = UnoRuntime.queryInterface( + XAccessible.class, m_document.getCurrentView().getControl( checkBox ) ); + + XAccessibleValue xValue = UnoRuntime.queryInterface( + XAccessibleValue.class, accessible.getAccessibleContext() ); + + xValue.setCurrentValue( Short.valueOf( state ) ); + } + + /* ------------------------------------------------------------------ */ + /** simulates a user selecting an entry in a list box + */ + private void simulateUserListBoxSelection( XPropertySet _listBox, String _selectEntry ) throws com.sun.star.uno.Exception + { + XListBox listBoxControl = UnoRuntime.queryInterface( + XListBox.class, m_document.getCurrentView().getControl( _listBox ) ); + listBoxControl.selectItem( _selectEntry, true ); + } + + /* ------------------------------------------------------------------ */ + /** simulates text input into the control belonging to the given model + */ + private void simulateUserTextInput( XPropertySet controlModel, String text ) throws com.sun.star.uno.Exception + { + XAccessible accessible = UnoRuntime.queryInterface( + XAccessible.class, m_document.getCurrentView().getControl( controlModel ) ); + + UnoRuntime.queryInterface( XServiceInfo.class, + accessible.getAccessibleContext() ); + + XAccessibleEditableText textAccess = UnoRuntime.queryInterface( + XAccessibleEditableText.class, accessible.getAccessibleContext() ); + + textAccess.setText( text ); + } +} diff --git a/forms/qa/integration/forms/ControlValidation.java b/forms/qa/integration/forms/ControlValidation.java new file mode 100644 index 000000000..dce4a23f2 --- /dev/null +++ b/forms/qa/integration/forms/ControlValidation.java @@ -0,0 +1,176 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.util.*; +import com.sun.star.lang.*; +import com.sun.star.beans.*; + +public class ControlValidation extends complexlib.ComplexTestCase implements com.sun.star.lang.XEventListener +{ + private DocumentHelper m_document; /// our current test document + private XMultiServiceFactory m_orb; /// our service factory + + @Override + public String[] getTestMethodNames() + { + return new String[] { + "interactiveValidation" + }; + } + + @Override + public String getTestObjectName() + { + return "Form Control Validation Test"; + } + + public static boolean isInteractiveTest() + { + return true; + } + + /* ------------------------------------------------------------------ */ + /* test framework */ + /* ------------------------------------------------------------------ */ + public void before() throws java.lang.Exception + { + m_orb = param.getMSF(); + } + + /* ------------------------------------------------------------------ */ + private void prepareTestStep( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + m_document = DocumentHelper.blankTextDocument( m_orb ); + m_document.getDocument( ).addEventListener( this ); + } + + /* ------------------------------------------------------------------ */ + public void after() + { + closeDocument(); + } + + /* ------------------------------------------------------------------ */ + /** closes our document, if we have an open one + */ + private void closeDocument() + { + try + { + // close our document + if ( m_document != null ) + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, + m_document.getDocument() ); + closeDoc.close( true ); + } + } + catch ( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + } + } + + /* ------------------------------------------------------------------ */ + /* public test methods */ + /* ------------------------------------------------------------------ */ + public void interactiveValidation() throws com.sun.star.uno.Exception, java.lang.Exception + { + prepareTestStep(); + + SingleControlValidation validation; + XPropertySet focusField; + + validation = new SingleControlValidation( m_document, 5, 5, "DatabaseFormattedField", new NumericValidator() ); + focusField = validation.getInputField(); + validation.setExplanatoryText( "Please enter a number between 0 and 100, with at most 1 decimal digit" ); + + validation = new SingleControlValidation( m_document, 90, 5, "DatabaseTextField", new TextValidator() ); + validation.setExplanatoryText( "Please enter a text whose length is a multiple of 3, and which does not contain the letter 'Z'" ); + + validation = new SingleControlValidation( m_document, 5, 55, "DatabaseDateField", new DateValidator() ); + validation.setExplanatoryText( "Please enter a date in the current month" ); + validation.getInputField().setPropertyValue( "Dropdown", Boolean.TRUE ); + + validation = new SingleControlValidation( m_document, 90, 55, "DatabaseTimeField", new TimeValidator() ); + validation.setExplanatoryText( "Please enter a time. Valid values are all full hours." ); + + validation = new SingleControlValidation( m_document, 5, 110, "DatabaseCheckBox", new BooleanValidator( false ) ); + validation.setExplanatoryText( "Please check (well, or uncheck) the box. Don't leave it in indetermined state." ); + validation.getInputField().setPropertyValue( "TriState", Boolean.TRUE ); + + validation = new SingleControlValidation( m_document, 90, 110, "DatabaseRadioButton", new BooleanValidator( true ), 3, 0 ); + validation.setExplanatoryText( "Please check any but the first button" ); + + validation = new SingleControlValidation( m_document, 5, 165, "DatabaseListBox", new ListSelectionValidator( ), 1, 24 ); + validation.setExplanatoryText( "Please select not more than two entries." ); + validation.getInputField().setPropertyValue( "MultiSelection", Boolean.TRUE ); + validation.getInputField().setPropertyValue( "StringItemList", new String[] { "first", "second", "third", "forth", "fivth" } ); + + // switch to alive mode + m_document.getCurrentView( ).toggleFormDesignMode( ); + m_document.getCurrentView( ).grabControlFocus( focusField ); + + // wait for the user telling us to exit + waitForUserInput(); + } + + /* ------------------------------------------------------------------ */ + /* internal methods */ + /* ------------------------------------------------------------------ */ + /** waits for the user to press a key (on the console where she started the java program) + or the document to be closed by the user. + @return + <TRUE/> if the user pressed a key on the console, <FALSE/> if she closed the document + */ + protected boolean waitForUserInput() throws java.lang.Exception + { + synchronized (this) + { + integration.forms.WaitForInput aWait = new integration.forms.WaitForInput( this ); + aWait.start(); + wait(); + + // if the waiter thread is done, the user pressed enter + boolean bKeyPressed = aWait.isDone(); + if ( !bKeyPressed ) + aWait.interrupt(); + + return bKeyPressed; + } + } + + /* ------------------------------------------------------------------ */ + /* XEventListener overridables */ + /* ------------------------------------------------------------------ */ + public void disposing( com.sun.star.lang.EventObject eventObject ) + { + if ( m_document.getDocument().equals( eventObject.Source ) ) + { + // notify ourself that we can stop waiting for user input + synchronized (this) + { + notify(); + } + } + } + +} diff --git a/forms/qa/integration/forms/ControlValidator.java b/forms/qa/integration/forms/ControlValidator.java new file mode 100644 index 000000000..fca013a19 --- /dev/null +++ b/forms/qa/integration/forms/ControlValidator.java @@ -0,0 +1,45 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +/** base class for components validating the content of form controls + */ +public abstract class ControlValidator implements com.sun.star.form.validation.XValidator +{ + + public void addValidityConstraintListener(com.sun.star.form.validation.XValidityConstraintListener xValidityConstraintListener) + { + } + + public void removeValidityConstraintListener(com.sun.star.form.validation.XValidityConstraintListener xValidityConstraintListener) + { + } + + protected boolean isVoid( Object Value ) + { + try + { + return ( com.sun.star.uno.AnyConverter.getType(Value).getTypeClass() + == com.sun.star.uno.TypeClass.VOID ); + } + catch( java.lang.ClassCastException e ) + { + } + return false; + } +} diff --git a/forms/qa/integration/forms/DateValidator.java b/forms/qa/integration/forms/DateValidator.java new file mode 100644 index 000000000..255ec6f6f --- /dev/null +++ b/forms/qa/integration/forms/DateValidator.java @@ -0,0 +1,80 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +public class DateValidator extends integration.forms.ControlValidator +{ + + public String explainInvalid( Object Value ) + { + try + { + if ( isVoid( Value ) ) + return "empty input"; + + com.sun.star.util.Date dateValue = (com.sun.star.util.Date)Value; + if ( isDedicatedInvalidDate( dateValue ) ) + return "this is no valid date"; + + if ( !isNextMonthsDate( dateValue ) ) + return "date must denote a day in the current month"; + } + catch( java.lang.Exception e ) + { + return "oops. What did you enter for this to happen?"; + } + return ""; + } + + public boolean isValid( Object Value ) + { + try + { + if ( isVoid( Value ) ) + return false; + + com.sun.star.util.Date dateValue = (com.sun.star.util.Date) + com.sun.star.uno.AnyConverter.toObject( + com.sun.star.util.Date.class, Value); + if ( isDedicatedInvalidDate( dateValue ) ) + return false; + + return isNextMonthsDate( dateValue ); + } + catch( java.lang.Exception e ) + { + e.printStackTrace( System.err ); + } + return false; + } + + private boolean isDedicatedInvalidDate( com.sun.star.util.Date dateValue ) + { + return ( dateValue.Day == 0 ) && ( dateValue.Month == 0 ) && ( dateValue.Year == 0 ); + } + + private boolean isNextMonthsDate( com.sun.star.util.Date dateValue ) + { + java.util.Calendar today = java.util.Calendar.getInstance(); + java.util.Calendar date = (java.util.Calendar) today.clone(); + today.set(java.util.Calendar.DATE, 1); + date.set(dateValue.Year, dateValue.Month -1, 1); // Month value is 0-based. e.g., 0 for January. + date.add(java.util.Calendar.MONTH, -1); + return date.compareTo(today) == 0; + } +} diff --git a/forms/qa/integration/forms/DocumentHelper.java b/forms/qa/integration/forms/DocumentHelper.java new file mode 100644 index 000000000..64caf0a15 --- /dev/null +++ b/forms/qa/integration/forms/DocumentHelper.java @@ -0,0 +1,375 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.beans.PropertyState; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexContainer; +import com.sun.star.container.XNameContainer; +import com.sun.star.document.MacroExecMode; +import com.sun.star.drawing.XDrawPage; +import com.sun.star.drawing.XDrawPageSupplier; +import com.sun.star.drawing.XDrawPages; +import com.sun.star.drawing.XDrawPagesSupplier; +import com.sun.star.form.XFormsSupplier; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XController; +import com.sun.star.frame.XFrame; +import com.sun.star.frame.XModel; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; +import com.sun.star.util.XModifiable; + +/**************************************************************************/ + +/**************************************************************************/ +/** provides a small wrapper around a document +*/ +public class DocumentHelper +{ + private final XMultiServiceFactory m_orb; + private XComponent m_documentComponent; + + /* ================================================================== */ + /* ------------------------------------------------------------------ */ + public DocumentHelper( XMultiServiceFactory orb, XComponent document ) + { + m_orb = orb; + m_documentComponent = document; + } + + /* ------------------------------------------------------------------ */ + protected static XComponent implLoadAsComponent( XMultiServiceFactory orb, String documentOrFactoryURL ) throws com.sun.star.uno.Exception + { + return implLoadAsComponent( orb, documentOrFactoryURL, new PropertyValue[0] ); + } + + /* ------------------------------------------------------------------ */ + private static XComponent implLoadAsComponent( XMultiServiceFactory orb, String documentOrFactoryURL, final PropertyValue[] i_args ) throws com.sun.star.uno.Exception + { + XComponentLoader aLoader = UnoRuntime.queryInterface( + XComponentLoader.class, + orb.createInstance( "com.sun.star.frame.Desktop" ) + ); + + XComponent document = dbfTools.queryComponent( + aLoader.loadComponentFromURL( documentOrFactoryURL, "_blank", 0, i_args ) + ); + return document; + } + + /* ------------------------------------------------------------------ */ + private static DocumentHelper implLoadDocument( XMultiServiceFactory orb, String documentOrFactoryURL ) throws com.sun.star.uno.Exception + { + return implLoadDocument( orb, documentOrFactoryURL, new PropertyValue[0] ); + } + + /* ------------------------------------------------------------------ */ + private static DocumentHelper implLoadDocument( XMultiServiceFactory orb, String documentOrFactoryURL, final PropertyValue[] i_args ) throws com.sun.star.uno.Exception + { + XComponent document = implLoadAsComponent( orb, documentOrFactoryURL, i_args ); + + XServiceInfo xSI = UnoRuntime.queryInterface( XServiceInfo.class, + document ); + if ( xSI.supportsService( "com.sun.star.sheet.SpreadsheetDocument" ) ) + return new SpreadsheetDocument( orb, document ); + return new DocumentHelper( orb, document ); + } + + /* ------------------------------------------------------------------ */ + public static DocumentHelper loadDocument( XMultiServiceFactory orb, String documentURL ) throws com.sun.star.uno.Exception + { + return implLoadDocument( orb, documentURL ); + } + + /* ------------------------------------------------------------------ */ + public static DocumentHelper blankTextDocument( XMultiServiceFactory orb ) throws com.sun.star.uno.Exception + { + return blankDocument( orb, DocumentType.WRITER ); + } + + + + /* ------------------------------------------------------------------ */ + public static DocumentHelper blankDocument( XMultiServiceFactory orb, DocumentType eType ) throws com.sun.star.uno.Exception + { + final PropertyValue[] args = new PropertyValue[] { + new PropertyValue( "MacroExecutionMode", -1, MacroExecMode.ALWAYS_EXECUTE, PropertyState.DIRECT_VALUE ) + }; + return implLoadDocument( orb, getDocumentFactoryURL( eType ), args ); + } + + /* ================================================================== */ + /* ------------------------------------------------------------------ */ + public XComponent getDocument( ) + { + return m_documentComponent; + } + + /* ------------------------------------------------------------------ */ + public boolean isModified() + { + XModifiable modify = query( XModifiable.class ); + return modify.isModified(); + } + + /* ------------------------------------------------------------------ */ + public <T> T query( Class<T> aInterfaceClass ) + { + return UnoRuntime.queryInterface( aInterfaceClass, m_documentComponent ); + } + + /* ------------------------------------------------------------------ */ + public XMultiServiceFactory getOrb( ) + { + return m_orb; + } + + /* ------------------------------------------------------------------ */ + /** retrieves the current view of the document + @return + the view component, queried for the interface described by aInterfaceClass + */ + public DocumentViewHelper getCurrentView( ) + { + // get the model interface for the document + XModel xDocModel = UnoRuntime.queryInterface(XModel.class, m_documentComponent ); + // get the current controller for the document - as a controller is tied to a view, + // this gives us the currently active view for the document. + XController xController = xDocModel.getCurrentController(); + + if ( classify() == DocumentType.CALC ) + return new SpreadsheetView( m_orb, this, xController ); + + return new DocumentViewHelper( m_orb, this, xController ); + } + + /* ------------------------------------------------------------------ */ + /** reloads the document + * + * The reload is done by dispatching the respective URL at a frame of the document. + * As a consequence, if you have references to a view of the document, or any interface + * of the document, they will become invalid. + * The Model instance itself, at which you called reload, will still be valid, it will + * automatically update its internal state after the reload. + * + * Another consequence is that if the document does not have a view at all, it cannot + * be reloaded. + */ + public void reload() throws Exception + { + DocumentViewHelper view = getCurrentView(); + XFrame frame = view.getController().getFrame(); + XModel oldModel = frame.getController().getModel(); + + getCurrentView().dispatch( ".uno:Reload" ); + + m_documentComponent = UnoRuntime.queryInterface( XComponent.class, + frame.getController().getModel() ); + + XModel newModel = getCurrentView().getController().getModel(); + if ( UnoRuntime.areSame( oldModel, newModel ) ) + throw new java.lang.IllegalStateException( "reload failed" ); + } + + /* ------------------------------------------------------------------ */ + /** creates a new form which is a child of the given form components container + + @param xParentContainer + The parent container for the new form + @param sInitialName + The initial name of the form. May be null, in this case the default (which + is an implementation detail) applies. + */ + private XIndexContainer createSubForm( XIndexContainer xParentContainer, String sInitialName ) + throws com.sun.star.uno.Exception + { + // create a new form + Object xNewForm = m_orb.createInstance( "com.sun.star.form.component.DataForm" ); + + // insert + xParentContainer.insertByIndex( xParentContainer.getCount(), xNewForm ); + + // set the name if necessary + if ( null != sInitialName ) + { + XPropertySet xFormProps = dbfTools.queryPropertySet( xNewForm ); + xFormProps.setPropertyValue( "Name", sInitialName ); + } + + // outta here + return UnoRuntime.queryInterface( XIndexContainer.class, xNewForm ); + } + + /* ------------------------------------------------------------------ */ + /** creates a new form which is a child of the given form components container + + @param aParentContainer + The parent container for the new form + @param sInitialName + The initial name of the form. May be null, in this case the default (which + is an implementation detail) applies. + */ + public XIndexContainer createSubForm( Object aParentContainer, String sInitialName ) + throws com.sun.star.uno.Exception + { + XIndexContainer xParentContainer = UnoRuntime.queryInterface( + XIndexContainer.class, aParentContainer ); + return createSubForm( xParentContainer, sInitialName ); + } + + /* ------------------------------------------------------------------ */ + /** creates a form which is a sibling of the given form + @param aForm + A sinbling of the to be created form. + + @param sInitialName + The initial name of the form. May be null, in this case the default (which + is an implementation detail) applies. + */ + public XIndexContainer createSiblingForm( Object aForm, String sInitialName ) throws com.sun.star.uno.Exception + { + // get the parent + XIndexContainer xContainer = (XIndexContainer)dbfTools.getParent( + aForm, XIndexContainer.class ); + // append a new form to this parent container + return createSubForm( xContainer, sInitialName ); + } + + + + /* ------------------------------------------------------------------ */ + /** returns a URL which can be used to create a document of a certain type + */ + public static String getDocumentFactoryURL( DocumentType eType ) + { + if ( eType == DocumentType.WRITER ) + return "private:factory/swriter"; + if ( eType == DocumentType.CALC ) + return "private:factory/scalc"; + if ( eType == DocumentType.DRAWING ) + return "private:factory/sdraw"; + if ( eType == DocumentType.XMLFORM ) + return "private:factory/swriter?slot=21053"; + return "private:factory/swriter"; + } + + /* ------------------------------------------------------------------ */ + /** classifies a document + */ + private DocumentType classify( ) + { + XServiceInfo xSI = UnoRuntime.queryInterface( + XServiceInfo.class, m_documentComponent ); + + if ( xSI.supportsService( "com.sun.star.text.TextDocument" ) ) + return DocumentType.WRITER; + else if ( xSI.supportsService( "com.sun.star.sheet.SpreadsheetDocument" ) ) + return DocumentType.CALC; + else if ( xSI.supportsService( "com.sun.star.drawing.DrawingDocument" ) ) + return DocumentType.DRAWING; + + return DocumentType.UNKNOWN; + } + + /* ------------------------------------------------------------------ */ + /** retrieves a com.sun.star.drawing.DrawPage of the document, denoted by index + * @param index + * the index of the draw page + * @throws + * com.sun.star.lang.IndexOutOfBoundsException + * com.sun.star.lang.WrappedTargetException + */ + protected XDrawPage getDrawPage( int index ) throws com.sun.star.lang.IndexOutOfBoundsException, com.sun.star.lang.WrappedTargetException + { + XDrawPagesSupplier xSuppPages = UnoRuntime.queryInterface( + XDrawPagesSupplier.class, getDocument() ); + XDrawPages xPages = xSuppPages.getDrawPages(); + + return UnoRuntime.queryInterface( XDrawPage.class, xPages.getByIndex( index ) ); + } + + /* ------------------------------------------------------------------ */ + /** retrieves the <type scope="com.sun.star.drawing">DrawPage</type> of the document + */ + protected XDrawPage getMainDrawPage( ) throws com.sun.star.uno.Exception + { + XDrawPage xReturn; + + // in case of a Writer document, this is rather easy: simply ask the XDrawPageSupplier + XDrawPageSupplier xSuppPage = UnoRuntime.queryInterface( + XDrawPageSupplier.class, getDocument() ); + if ( null != xSuppPage ) + xReturn = xSuppPage.getDrawPage(); + else + { // the model itself is no draw page supplier - okay, it may be a Writer or Calc document + // (or any other multi-page document) + XDrawPagesSupplier xSuppPages = UnoRuntime.queryInterface( + XDrawPagesSupplier.class, getDocument() ); + XDrawPages xPages = xSuppPages.getDrawPages(); + + xReturn = UnoRuntime.queryInterface( XDrawPage.class, xPages.getByIndex( 0 ) ); + + // Note that this is no really error-proof code: If the document model does not support the + // XDrawPagesSupplier interface, or if the pages collection returned is empty, this will break. + } + + return xReturn; + } + + /* ------------------------------------------------------------------ */ + /** retrieves the root of the hierarchy of form components + */ + protected XNameContainer getFormComponentTreeRoot( ) throws com.sun.star.uno.Exception + { + XFormsSupplier xSuppForms = UnoRuntime.queryInterface( + XFormsSupplier.class, getMainDrawPage( ) ); + + XNameContainer xFormsCollection = null; + if ( null != xSuppForms ) + { + xFormsCollection = xSuppForms.getForms(); + } + return xFormsCollection; + } + + /* ------------------------------------------------------------------ */ + /** creates a component at the service factory provided by the document + */ + public XInterface createInstance( String serviceSpecifier ) throws com.sun.star.uno.Exception + { + XMultiServiceFactory xORB = UnoRuntime.queryInterface( XMultiServiceFactory.class, + m_documentComponent ); + return (XInterface)xORB.createInstance( serviceSpecifier ); + } + + /* ------------------------------------------------------------------ */ + /** creates a component at the service factory provided by the document + */ + public XInterface createInstanceWithArguments( String serviceSpecifier, Object[] arguments ) throws com.sun.star.uno.Exception + { + XMultiServiceFactory xORB = UnoRuntime.queryInterface( XMultiServiceFactory.class, + m_documentComponent ); + return (XInterface) xORB.createInstanceWithArguments( serviceSpecifier, arguments ); + } +} + diff --git a/forms/qa/integration/forms/DocumentType.java b/forms/qa/integration/forms/DocumentType.java new file mode 100644 index 000000000..105c31fc2 --- /dev/null +++ b/forms/qa/integration/forms/DocumentType.java @@ -0,0 +1,39 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +/** a helper "enumeration class" for classifying a document type +*/ +public class DocumentType extends com.sun.star.uno.Enum +{ + private DocumentType( int value ) + { + super( value ); + } + + + + public static final DocumentType WRITER = new DocumentType(0); + public static final DocumentType CALC = new DocumentType(1); + public static final DocumentType DRAWING = new DocumentType(2); + public static final DocumentType XMLFORM = new DocumentType(3); + public static final DocumentType UNKNOWN = new DocumentType(-1); + + +} + diff --git a/forms/qa/integration/forms/DocumentViewHelper.java b/forms/qa/integration/forms/DocumentViewHelper.java new file mode 100644 index 000000000..d936ea267 --- /dev/null +++ b/forms/qa/integration/forms/DocumentViewHelper.java @@ -0,0 +1,221 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +/**************************************************************************/ + +import com.sun.star.awt.XControl; +import com.sun.star.awt.XControlModel; +import com.sun.star.awt.XWindow; +import com.sun.star.awt.XToolkitExperimental; +import com.sun.star.beans.PropertyValue; +import com.sun.star.form.XForm; +import com.sun.star.form.runtime.XFormController; +import com.sun.star.frame.XController; +import com.sun.star.frame.XDispatch; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.URL; +import com.sun.star.util.XURLTransformer; +import com.sun.star.view.XControlAccess; +import com.sun.star.view.XFormLayerAccess; +import org.openoffice.xforms.XMLDocument; + +/**************************************************************************/ +/** provides a small wrapper around a document view +*/ +public class DocumentViewHelper +{ + private final XMultiServiceFactory m_orb; + private final XController m_controller; + private final DocumentHelper m_document; + + /* ------------------------------------------------------------------ */ + final protected XController getController() + { + return m_controller; + } + + /* ------------------------------------------------------------------ */ + final protected DocumentHelper getDocument() + { + return m_document; + } + + /* ------------------------------------------------------------------ */ + public DocumentViewHelper( XMultiServiceFactory orb, DocumentHelper document, XController controller ) + { + m_orb = orb; + m_document = document; + m_controller = controller; + } + + /* ------------------------------------------------------------------ */ + /** Quick access to a given interface of the view + @param aInterfaceClass + the class of the interface which shall be returned + */ + public <T> T query( Class<T> aInterfaceClass ) + { + return UnoRuntime.queryInterface( aInterfaceClass, m_controller ); + } + + /* ------------------------------------------------------------------ */ + /** retrieves a dispatcher for the given URL, obtained at the current view of the document + @param aURL + a one-element array. The first element must contain a valid + <member scope="com.sun.star.util">URL::Complete</member> value. Upon return, the URL is correctly + parsed. + @return + the dispatcher for the URL in question + */ + private XDispatch getDispatcher( URL[] aURL ) throws java.lang.Exception + { + XDispatch xReturn = null; + + // go get the current view + XController xController = query( XController.class ); + // go get the dispatch provider of its frame + XDispatchProvider xProvider = UnoRuntime.queryInterface( + XDispatchProvider.class, xController.getFrame() ); + if ( null != xProvider ) + { + // need a URLTransformer + XURLTransformer xTransformer = UnoRuntime.queryInterface( + XURLTransformer.class, m_orb.createInstance( "com.sun.star.util.URLTransformer" ) ); + xTransformer.parseStrict( aURL ); + + xReturn = xProvider.queryDispatch( aURL[0], "", 0 ); + } + return xReturn; + } + + /* ------------------------------------------------------------------ */ + /** retrieves a dispatcher for the given URL, obtained at the current view of the document + */ + public XDispatch getDispatcher( String url ) throws java.lang.Exception + { + URL[] aURL = new URL[] { new URL() }; + aURL[0].Complete = url; + return getDispatcher( aURL ); + } + + /* ------------------------------------------------------------------ */ + /** dispatches the given URL into the view, if there's a dispatcher for it + + @return + <TRUE/> if the URL was successfully dispatched + */ + public boolean dispatch( String url ) throws java.lang.Exception + { + URL[] completeURL = new URL[] { new URL() }; + completeURL[0].Complete = url; + XDispatch dispatcher = getDispatcher( completeURL ); + if ( dispatcher == null ) + return false; + + PropertyValue[] aDummyArgs = new PropertyValue[] { }; + dispatcher.dispatch( completeURL[0], aDummyArgs ); + return true; + } + + /* ------------------------------------------------------------------ */ + /** retrieves a control within the current view of a document + @param xModel + specifies the control model whose control should be located + @return + the control tied to the model + */ + public XControl getControl( XControlModel xModel ) throws com.sun.star.uno.Exception + { + // the current view of the document + XControlAccess xCtrlAcc = query( XControlAccess.class ); + // delegate the task of looking for the control + return xCtrlAcc.getControl( xModel ); + } + + /* ------------------------------------------------------------------ */ + public XControl getControl( Object aModel ) throws com.sun.star.uno.Exception + { + XControlModel xModel = UnoRuntime.queryInterface( XControlModel.class, aModel ); + return getControl( xModel ); + } + + /* ------------------------------------------------------------------ */ + public <T> T getControl( Object aModel, Class<T> aInterfaceClass ) throws com.sun.star.uno.Exception + { + XControlModel xModel = UnoRuntime.queryInterface( XControlModel.class, aModel ); + return UnoRuntime.queryInterface( aInterfaceClass, getControl( xModel ) ); + } + + /* ------------------------------------------------------------------ */ + /** retrieves the form controller for a given logical form + */ + private XFormController getFormController( XForm _form ) + { + XFormLayerAccess formLayerAccess = query( XFormLayerAccess.class ); + return formLayerAccess.getFormController( _form ); + } + + /* ------------------------------------------------------------------ */ + /** retrieves the form controller for a given logical form + */ + public XFormController getFormController( Object _form ) + { + return getFormController( UnoRuntime.queryInterface( XForm.class, _form )); + } + + /* ------------------------------------------------------------------ */ + /** toggles the design mode of the form layer of active view of our sample document + */ + protected void toggleFormDesignMode( ) throws java.lang.Exception + { + if ( m_document instanceof XMLDocument ) + dispatch( ".uno:SwitchXFormsDesignMode" ); + else + dispatch( ".uno:SwitchControlDesignMode" ); + // at least SwitchControlDesignMode is async, so wait for it to be done + XToolkitExperimental xToolkit = UnoRuntime.queryInterface( + XToolkitExperimental.class, + m_orb.createInstance("com.sun.star.awt.Toolkit")); + xToolkit.processEventsToIdle(); + } + + /* ------------------------------------------------------------------ */ + /** sets the focus to a specific control + @param xModel + a control model. The focus is set to that control which is part of our view + and associated with the given model. + */ + public void grabControlFocus( Object xModel ) throws com.sun.star.uno.Exception + { + // look for the control from the current view which belongs to the model + XControl xControl = getControl( xModel ); + + // the focus can be set to an XWindow only + XWindow xControlWindow = UnoRuntime.queryInterface( XWindow.class, + xControl ); + + // grab the focus + xControlWindow.setFocus(); + } + + +} + diff --git a/forms/qa/integration/forms/FormComponent.java b/forms/qa/integration/forms/FormComponent.java new file mode 100644 index 000000000..1861c084e --- /dev/null +++ b/forms/qa/integration/forms/FormComponent.java @@ -0,0 +1,114 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.UnoRuntime; +import com.sun.star.form.XFormsSupplier; +import com.sun.star.container.XNameAccess; +import com.sun.star.container.XIndexAccess; +import com.sun.star.container.XChild; +import com.sun.star.container.XNamed; +import com.sun.star.drawing.XDrawPage; + +public class FormComponent +{ + private final Object m_component; + private final XNameAccess m_nameAccess; + private final XIndexAccess m_indexAccess; + + /* ------------------------------------------------------------------ */ + private FormComponent() + { + m_component = null; + m_nameAccess = null; + m_indexAccess = null; + } + + /* ------------------------------------------------------------------ */ + public FormComponent( XDrawPage drawPage ) + { + XFormsSupplier supp = UnoRuntime.queryInterface( + XFormsSupplier.class, drawPage ); + m_component = supp.getForms(); + + m_nameAccess = (XNameAccess)m_component; + m_indexAccess = UnoRuntime.queryInterface( + XIndexAccess.class, m_component ); + UnoRuntime.queryInterface( + XChild.class, m_component ); + UnoRuntime.queryInterface( + XNamed.class, m_component ); + } + + /* ------------------------------------------------------------------ */ + private FormComponent( Object element ) + { + m_component = element; + m_nameAccess = UnoRuntime.queryInterface( + XNameAccess.class, m_component ); + m_indexAccess = UnoRuntime.queryInterface( + XIndexAccess.class, m_component ); + UnoRuntime.queryInterface( + XChild.class, m_component ); + UnoRuntime.queryInterface( + XNamed.class, m_component ); + } + + /* ------------------------------------------------------------------ */ + /** Quick access to a given interface of the view + @param aInterfaceClass + the class of the interface which shall be returned + */ + public <T> T query( Class<T> aInterfaceClass ) + { + return UnoRuntime.queryInterface( aInterfaceClass, m_component ); + } + + /* ------------------------------------------------------------------ */ + public FormComponent getByName( String name ) + { + try + { + if ( m_nameAccess != null ) + return new FormComponent( m_nameAccess.getByName( name ) ); + } + catch( com.sun.star.uno.Exception e ) + { + System.err.println( e ); + e.printStackTrace( System.err ); + } + return new FormComponent(); + } + + /* ------------------------------------------------------------------ */ + public FormComponent getByIndex( int index ) + { + try + { + if ( m_indexAccess != null ) + return new FormComponent( m_indexAccess.getByIndex( index ) ); + } + catch( com.sun.star.uno.Exception e ) + { + System.err.println( e ); + e.printStackTrace( System.err ); + } + return new FormComponent(); + } + +} diff --git a/forms/qa/integration/forms/FormControlTest.java b/forms/qa/integration/forms/FormControlTest.java new file mode 100644 index 000000000..773a0197a --- /dev/null +++ b/forms/qa/integration/forms/FormControlTest.java @@ -0,0 +1,951 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.awt.XImageProducer; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XNameAccess; +import com.sun.star.form.runtime.XFormController; +import com.sun.star.form.XImageProducerSupplier; +import com.sun.star.frame.XDispatch; +import com.sun.star.lang.EventObject; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdb.SQLErrorEvent; +import com.sun.star.sdb.XSQLErrorBroadcaster; +import com.sun.star.sdb.XSQLErrorListener; +import com.sun.star.sdbc.XDataSource; +import com.sun.star.sdbc.XResultSet; +import com.sun.star.sdbc.XResultSetUpdate; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XNamingService; +import com.sun.star.util.URL; +import com.sun.star.util.XCloseable; +import com.sun.star.util.XURLTransformer; +import connectivity.tools.HsqlDatabase; +import connectivity.tools.sdb.Connection; +import java.io.FileOutputStream; + + +public class FormControlTest extends complexlib.ComplexTestCase implements XSQLErrorListener +{ + private static String s_tableName = "CTC_form_controls"; + + private HsqlDatabase m_databaseDocument; + private XDataSource m_dataSource; + private XPropertySet m_dataSourceProps; + private XMultiServiceFactory m_orb; + private DocumentHelper m_document; + private FormLayer m_formLayer; + private XPropertySet m_masterForm; + private String m_sImageURL; + private SQLErrorEvent m_mostRecentErrorEvent; + + private static final String m_dataSourceName = "integration.forms.FormControlTest"; + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkFirstRow", + "checkInsertRow", + "checkImageControl", + "checkCrossUpdates_checkBox", + "checkCrossUpdates_radioButton", + "checkRowUpdates", + "checkEmptyIsNull" + }; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Database Form Controls Test"; + } + + /* ------------------------------------------------------------------ */ + /// pre-test initialization + public void before() throws com.sun.star.uno.Exception, java.lang.Exception + { + // ensure that we have a data source to work with, and the required tables + if ( !ensureDataSource() || !ensureTables() ) + { + failed( "could not access the required data source or table therein." ); + return; + } + + // create the document which we work on + createSampleDocument(); + + createImageFile(); + } + + /* ------------------------------------------------------------------ */ + public void checkFirstRow() throws com.sun.star.uno.Exception, java.lang.Exception + { + moveToFirst(); + + // and check the content of the various controls + if ( !checkRadios( (short)1, (short)0, (short)0 ) + || !checkDoubleValue( 1, "ID", "Value" ) + || !checkDoubleValue( 42, "f_integer", "EffectiveValue" ) + || !checkStringValue( "the answer", "f_text", "Text" ) + || !checkDoubleValue( 0.12, "f_decimal", "Value" ) + || !checkIntValue ( 20030922, "f_date", "Date" ) + || !checkIntValue ( 15000000, "f_time", "Time" ) + || !checkIntValue ( 20030923, "f_timestamp_date", "Date" ) + || !checkIntValue ( 17152300, "f_timestamp_time", "Time" ) + || !checkShortValue ( (short)1, "f_tinyint", "State" ) + ) + { + failed( "checking the content of one or more controls on the first row failed (see the log for details)" ); + return; + } + } + + /* ------------------------------------------------------------------ */ + public void checkInsertRow() throws com.sun.star.uno.Exception, java.lang.Exception + { + // move the cursor to the insert row + moveToInsertRow(); + + // and check the content of the various controls + if ( !verifyCleanInsertRow() ) + { + failed( "checking the content of one or more controls on the insert row failed (see the log for details)" ); + return; + } + } + + /* ------------------------------------------------------------------ */ + /// some tests with the image control + public void checkImageControl() throws com.sun.star.uno.Exception, java.lang.Exception + { + // since we did not yet insert any image, the control should not display one ... + moveToFirst(); + if ( !verifyReferenceImage( new byte[0] ) ) + { + failed( "image control failed to display empty image" ); + return; + } + + // check if the image control is able to insert our sample image into the database + // insert an + XPropertySet xImageModel = getControlModel( "f_blob" ); + xImageModel.setPropertyValue( "ImageURL", m_sImageURL ); + + if ( !verifyReferenceImage( getSamplePictureBytes() ) ) + { + failed( "image control does not display the sample image as required" ); + return; + } + + // save the record + saveRecordByUI(); + + // still needs to be the sample image + if ( !verifyReferenceImage( getSamplePictureBytes() ) ) + { + failed( "image control does not, after saving the record, display the sample image as required" ); + return; + } + + // on the next record, the image should be empty + moveToNext(); + if ( !verifyReferenceImage( new byte[0] ) ) + { + failed( "image control failed to display empty image, after coming from a non-empty image" ); + return; + } + + // back to the record where we just inserted the image, it should be our sample image + moveToFirst(); + if ( !verifyReferenceImage( getSamplePictureBytes() ) ) + { + failed( "image control does not, after coming back to the record, display the sample image as required" ); + return; + } + + // okay, now remove the image + xImageModel.setPropertyValue( "ImageURL", "" ); + if ( !verifyReferenceImage( new byte[0] ) ) + { + failed( "image control failed to remove the image" ); + return; + } + nextRecordByUI(); + previousRecordByUI(); + if ( !verifyReferenceImage( new byte[0] ) ) + { + failed( "image still there after coming back, though we just removed it" ); + return; + } + } + + /* ------------------------------------------------------------------ */ + /** This is both a test for controls which are bound to the same column (they must reflect + * each others updates), and for the immediate updates which need to happen for both check + * boxes and radio buttons: They must commit their content to the underlying column as soon + * as the change is made, *not* only upon explicit commit + */ + public void checkCrossUpdates_checkBox() throws com.sun.star.uno.Exception, java.lang.Exception + { + // move to the first record + moveToFirst(); + if ( !checkShortValue ( (short)1, "f_tinyint", "State" ) + || !checkDoubleValue( 1, "f_tinyint_format", "EffectiveValue" ) + ) + { + failed( "huh? inconsistence in the test!" ); + // we created the sample data in a way that the f_tinyint field should contain a "1" at the first + // record. We already asserted the proper function of the check box in checkFirstRow, so if this + // fails here, the script became inconsistent + return; + } + + XPropertySet checkModel = getControlModel( "f_tinyint" ); + checkModel.setPropertyValue( "State", Short.valueOf( (short)0 ) ); + + // setting the state of the check box needs to be reflected in the formatted field immediately + if ( !checkDoubleValue( 0, "f_tinyint_format", "EffectiveValue" ) ) + { + failed( "cross-update failed: updating the check box should result in updating the same-bound formatted field (1)!" ); + return; + } + + // same for the "indetermined" state of the check box + checkModel.setPropertyValue( "State", Short.valueOf( (short)2 ) ); + if ( !checkNullValue( "f_tinyint_format", "EffectiveValue" ) ) + { + failed( "cross-update failed: updating the check box should result in updating the same-bound formatted field (2)!" ); + return; + } + + // undo the changes done so far + undoRecordByUI(); + // and see if this is properly reflected in the controls + if ( !checkShortValue ( (short)1, "f_tinyint", "State" ) + || !checkDoubleValue( 1, "f_tinyint_format", "EffectiveValue" ) + ) + { + failed( "either the check box or the formatted field failed to recognize the UNDO!" ); + return; + } + + // the other way round - when changing the formatted field - the change should *not* + // be reflected to the check box, since the formatted field needs an explicit commit + XPropertySet tinyFormattedModel = getControlModel( "f_tinyint_format" ); + m_document.getCurrentView().grabControlFocus( tinyFormattedModel ); + m_formLayer.userTextInput( tinyFormattedModel, "0" ); + if ( !checkShortValue ( (short)1, "f_tinyint", "State" ) + ) + { + failed( "the check box should not be updated here! (did the formatted model commit immediately?)" ); + return; + } + + // set the focus to *any* other control (since we just have it at hand, we use the check box control) + // this should result in the formatted control being committed, and thus in the check box updating + m_document.getCurrentView().grabControlFocus( checkModel ); + if ( !checkShortValue ( (short)0, "f_tinyint", "State" ) + ) + { + failed( "formatted field did not commit (or check box did not update)" ); + return; + } + + // undo the changes done so far, so we leave the document in a clean state for the next test + undoRecordByUI(); + } + + /* ------------------------------------------------------------------ */ + /** very similar to checkCrossUpdates_checkBox - does nearly the same for the radio buttons. See there for more + * explanations. + */ + public void checkCrossUpdates_radioButton() throws com.sun.star.uno.Exception, java.lang.Exception + { + // move to the first record + moveToFirst(); + if ( !checkRadios( (short)1, (short)0, (short)0 ) + || !checkStringValue( "none", "f_text_enum_text", "Text" ) + ) + { + failed( "huh? inconsistence in the test!" ); + return; + } + + XPropertySet radioModel = getRadioModel( "radio_group", "normal" ); + radioModel.setPropertyValue( "State", Short.valueOf( (short)1 ) ); + + // setting the state of the radio button needs to be reflected in the formatted field immediately + if ( !checkStringValue( "normal", "f_text_enum_text", "Text" ) ) + { + failed( "cross-update failed: updating the radio button should result in updating the same-bound text field (1)!" ); + return; + } + + // same for the "indetermined" state of the check box + getRadioModel( "radio_group", "important" ).setPropertyValue( "State", Short.valueOf( (short)1 ) ); + if ( !checkStringValue( "important", "f_text_enum_text", "Text" ) ) + { + failed( "cross-update failed: updating the radio button should result in updating the same-bound text field (2)!" ); + return; + } + + // undo the changes done so far + undoRecordByUI(); + // and see if this is properly reflected in the controls + if ( !checkRadios( (short)1, (short)0, (short)0 ) + || !checkStringValue( "none", "f_text_enum_text", "Text" ) + ) + { + failed( "either the radio button or the text field failed to recognize the UNDO!" ); + return; + } + + // the other way round - when changing the formatted field - the change should *not* + // be reflected to the check box, since the formatted field needs an explicit commit + XPropertySet textModel = getControlModel( "f_text_enum_text" ); + m_document.getCurrentView().grabControlFocus( textModel ); + m_formLayer.userTextInput( textModel, "normal" ); + if ( !checkRadios( (short)1, (short)0, (short)0 ) + ) + { + failed( "the radio buttons should not be updated here! (did the formatted model commit immediately?)" ); + return; + } + + // set the focus to *any* other control (since we just have it at hand, we use the check box control) + // this should result in the formatted control being committed, and thus in the check box updating + m_document.getCurrentView().grabControlFocus( radioModel ); + if ( !checkRadios( (short)0, (short)1, (short)0 ) + ) + { + failed( "text field did not commit (or radio button did not update)" ); + return; + } + + // undo the changes done so far, so we leave the document in a clean state for the next test + undoRecordByUI(); + } + + /* ------------------------------------------------------------------ */ + /** some tests with updating the table via our controls + */ + public void checkRowUpdates() throws com.sun.star.uno.Exception, java.lang.Exception + { + // start with inserting a new record + moveToInsertRow(); + assure( "insert row not in expected clean state", verifyCleanInsertRow() ); + + userTextInput( "ID", "3", true ); + userTextInput( "f_integer", "729", true ); + userTextInput( "f_text", "test", true ); + userTextInput( "f_decimal", "152343", true ); + userTextInput( "f_date", "31.12.1999", true ); + userTextInput( "f_time", "23:59:59", true ); + + // move to the next row, this should automatically commit the changes we made + nextRecordByUI(); + // and back to the row we just inserted + previousRecordByUI(); + + if ( !checkDoubleValue( 3, "ID", "Value" ) + || !checkDoubleValue( 729, "f_integer", "EffectiveValue" ) + || !checkStringValue( "test", "f_text", "Text" ) + || !checkDoubleValue( 152343, "f_decimal", "Value" ) + || !checkIntValue ( 19991231, "f_date", "Date" ) + || !checkIntValue ( 23595900, "f_time", "Time" ) + ) + { + failed( "the changes we made on the insert row have not been committed" ); + return; + } + + // now change the data, to see if regular updates work, too + userTextInput( "ID", "4", true ); + userTextInput( "f_integer", "618", true ); + userTextInput( "f_text", "yet another stupid, meaningless text", true ); + userTextInput( "f_required_text", "this must not be NULL", true ); + userTextInput( "f_decimal", "4562", true ); + userTextInput( "f_date", "26.03.2004", true ); + userTextInput( "f_time", "17:05:00", true ); + + // move to the next row, this should automatically commit the changes we made + nextRecordByUI(); + // and back to the row we just inserted + previousRecordByUI(); + + if ( !checkDoubleValue( 4, "ID", "Value" ) + || !checkDoubleValue( 618, "f_integer", "EffectiveValue" ) + || !checkStringValue( "yet another stupid, meaningless text", "f_text", "Text" ) + || !checkDoubleValue( 4562, "f_decimal", "Value" ) + || !checkIntValue ( 20040326, "f_date", "Date" ) + || !checkIntValue ( 17050000, "f_time", "Time" ) + ) + { + failed( "the changes we made on the insert row have not been committed" ); + return; + } + + m_document.getCurrentView().grabControlFocus( getControlModel( "ID" ) ); + } + + /* ------------------------------------------------------------------ */ + /** checks the "ConvertEmptyToNull" property behavior of an edit control + * + */ + public void checkEmptyIsNull() throws com.sun.star.uno.Exception, java.lang.Exception + { + // start with inserting a new record + moveToInsertRow(); + assure( "insert row not in expected clean state", verifyCleanInsertRow() ); + + // make an input in any field, but leave the edit control which is bound to a required field + // empty + userTextInput( "ID", "5", true ); + userTextInput( "f_text", "more text", true ); + + // this should *not* fail. Even if we did not input anything into the control bound to the + // f_required_text column, this control's reset (done when moving to the insertion row) is + // expected to write an empty string into its bound column, since its EmptyIsNULL property + // is set to FALSE + // (#i92471#) + m_mostRecentErrorEvent = null; + nextRecordByUI(); + assure( "updating an incomplete record did not work as expected", m_mostRecentErrorEvent == null ); + } + + /* ------------------------------------------------------------------ */ + private boolean verifyCleanInsertRow( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + // and check the content of the various controls + return ( checkRadios( (short)0, (short)0, (short)0 ) + && checkShortValue( (short)2, "f_tinyint", "State" ) + && checkStringValue( "", "f_text", "Text" ) + && checkNullValue( "ID", "Value" ) + && checkNullValue( "f_integer", "EffectiveValue" ) + && checkNullValue( "f_decimal", "Value" ) + ); + } + + /* ------------------------------------------------------------------ */ + /// post-test cleanup + public void after() throws com.sun.star.uno.Exception, java.lang.Exception + { + // close our document + if ( m_document != null ) + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, + m_document.getDocument() ); + closeDoc.close( true ); + } + } + + + /* ------------------------------------------------------------------ */ + private boolean ensureDataSource() throws Exception + { + m_orb = param.getMSF(); + + XNameAccess databaseContext = UnoRuntime.queryInterface( XNameAccess.class, + m_orb.createInstance( "com.sun.star.sdb.DatabaseContext" ) ); + XNamingService namingService = UnoRuntime.queryInterface( XNamingService.class, + databaseContext ); + + // revoke the data source, if it previously existed + if ( databaseContext.hasByName( m_dataSourceName ) ) + namingService.revokeObject( m_dataSourceName ); + + // create a new ODB file, and register it with its URL + m_databaseDocument = new HsqlDatabase( m_orb ); + String documentURL = m_databaseDocument.getDocumentURL(); + namingService.registerObject( m_dataSourceName, databaseContext.getByName( documentURL ) ); + + m_dataSource = UnoRuntime.queryInterface( XDataSource.class, + databaseContext.getByName( m_dataSourceName ) ); + m_dataSourceProps = dbfTools.queryPropertySet( m_dataSource ); + + XPropertySet dataSourceSettings = UnoRuntime.queryInterface( XPropertySet.class, + m_dataSourceProps.getPropertyValue( "Settings" ) ); + dataSourceSettings.setPropertyValue( "FormsCheckRequiredFields", Boolean.FALSE ); + + return m_dataSource != null; + } + + /* ------------------------------------------------------------------ */ + /** retrieves the control model with the given name + */ + private XPropertySet getControlModel( String name ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XNameAccess nameAccess = UnoRuntime.queryInterface( XNameAccess.class, + m_masterForm ); + return UnoRuntime.queryInterface( XPropertySet.class, + nameAccess.getByName( name ) ); + } + + /* ------------------------------------------------------------------ */ + private void createSampleDocument() throws com.sun.star.uno.Exception, java.lang.Exception + { + + m_document = DocumentHelper.blankTextDocument( m_orb ); + m_formLayer = new FormLayer( m_document ); + + // insert some controls + XPropertySet xIDField = m_formLayer.insertControlLine( "DatabaseNumericField", "ID", "", 3 ); + m_formLayer.insertControlLine( "DatabaseFormattedField","f_integer", "", 11 ); + m_formLayer.insertControlLine( "DatabaseTextField", "f_text", "", 19 ); + XPropertySet xReqField = m_formLayer.insertControlLine( "DatabaseTextField", "f_required_text", "", 27 ); + m_formLayer.insertControlLine( "DatabaseNumericField", "f_decimal", "", 35 ); + m_formLayer.insertControlLine( "DatabaseDateField", "f_date", "", 43 ); + XPropertySet xTimeField = m_formLayer.insertControlLine( "DatabaseTimeField", "f_time", "", 51 ); + m_formLayer.insertControlLine( "DatabaseDateField", "f_timestamp", "_date", 59 ); + m_formLayer.insertControlLine( "DatabaseTimeField", "f_timestamp", "_time", 67 ); + XPropertySet xImageField = m_formLayer.insertControlLine( "DatabaseImageControl", "f_blob", "", 2, 75, 40 ); + m_formLayer.insertControlLine( "DatabaseTextField", "f_text_enum", "_text", 80, 25, 6 ); + XPropertySet xCheckBox = m_formLayer.insertControlLine( "DatabaseCheckBox", "f_tinyint", "", 80, 33, 6 ); + m_formLayer.insertControlLine( "DatabaseFormattedField","f_tinyint", "_format",80, 41, 6 ); + m_formLayer.insertControlLine( "DatabaseTextField", "dummy", "", 150 ); + + xIDField.setPropertyValue( "DecimalAccuracy", Short.valueOf( (short)0 ) ); + xImageField.setPropertyValue( "ScaleImage", Boolean.TRUE ); + xImageField.setPropertyValue( "Tabstop", Boolean.TRUE ); + xCheckBox.setPropertyValue( "TriState", Boolean.TRUE ); + xCheckBox.setPropertyValue( "DefaultState", Short.valueOf( (short)2 ) ); + xTimeField.setPropertyValue( "TimeFormat", Short.valueOf( (short)1 ) ); + xTimeField.setPropertyValue( "TimeMax", Integer.valueOf( 23595999 ) ); + xReqField.setPropertyValue( "ConvertEmptyToNull", Boolean.FALSE ); + + // the logical form + m_masterForm = (XPropertySet)dbfTools.getParent( xIDField, XPropertySet.class ); + m_masterForm.setPropertyValue( "DataSourceName", m_dataSourceProps.getPropertyValue( "Name" ) ); + m_masterForm.setPropertyValue( "CommandType", Integer.valueOf( CommandType.TABLE ) ); + m_masterForm.setPropertyValue( "Command", s_tableName ); + + insertRadio( 3, "none", "none" ); + insertRadio( 10, "normal", "normal" ); + insertRadio( 17, "important", "important" ); + + // switch the forms into data entry mode + m_document.getCurrentView( ).toggleFormDesignMode( ); + + XFormController masterFormController = m_document.getCurrentView().getFormController( m_masterForm ); + XSQLErrorBroadcaster errorBroadcaster = UnoRuntime.queryInterface( XSQLErrorBroadcaster.class, + masterFormController ); + errorBroadcaster.addSQLErrorListener( this ); + + // set the focus to the ID control + m_document.getCurrentView().grabControlFocus( xIDField ); + } + + /* ------------------------------------------------------------------ */ + private void insertRadio( int nYPos, String label, String refValue ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet xRadio = m_formLayer.createControlAndShape( "DatabaseRadioButton", 106, nYPos, 25, 6 ); + xRadio.setPropertyValue( "Label", label ); + xRadio.setPropertyValue( "RefValue", refValue ); + xRadio.setPropertyValue( "Name", "radio_group" ); + xRadio.setPropertyValue( "DataField", "f_text_enum"); + } + + /* ------------------------------------------------------------------ */ + private String getCreateTableStatement( ) + { + String sCreateTableStatement = "CREATE TABLE \"" + s_tableName + "\" ("; + sCreateTableStatement += "\"ID\" INTEGER NOT NULL PRIMARY KEY,"; + sCreateTableStatement += "\"f_integer\" INTEGER default NULL,"; + sCreateTableStatement += "\"f_text\" VARCHAR(50) default NULL,"; + sCreateTableStatement += "\"f_required_text\" VARCHAR(50) NOT NULL,"; + sCreateTableStatement += "\"f_decimal\" DECIMAL(10,2) default NULL,"; + sCreateTableStatement += "\"f_date\" DATE default NULL,"; + sCreateTableStatement += "\"f_time\" TIME default NULL,"; + sCreateTableStatement += "\"f_timestamp\" DATETIME default NULL,"; + sCreateTableStatement += "\"f_blob\" VARBINARY,"; + sCreateTableStatement += "\"f_text_enum\" VARCHAR(50) default NULL,"; + sCreateTableStatement += "\"f_tinyint\" TINYINT default NULL"; + sCreateTableStatement += ");"; + return sCreateTableStatement; + } + + /* ------------------------------------------------------------------ */ + private String[] getSampleDataValueString( ) throws java.lang.Exception + { + String[] aValues = new String[] { + "1,42,'the answer','foo',0.12,'2003-09-22','15:00:00','2003-09-23 17:15:23',NULL,'none',1", + "2,13,'the question','bar',12.43,'2003-09-24','16:18:00','2003-09-24 08:45:12',NULL,'none',0" + }; + return aValues; + } + + /* ------------------------------------------------------------------ */ + private boolean ensureTables() throws com.sun.star.uno.Exception, java.lang.Exception + { + Connection connection = new Connection( m_dataSource.getConnection( "", "" ) ); + + // drop the table, if it already exists + if ( !implExecuteStatement( "DROP TABLE \"" + s_tableName + "\" IF EXISTS" ) + || !implExecuteStatement( getCreateTableStatement() ) + ) + { + failed( "could not create the required sample table!" ); + return false; + } + + String sInsertionPrefix = "INSERT INTO \"" + s_tableName + "\" VALUES ("; + String[] aValues = getSampleDataValueString(); + for ( int i=0; i<aValues.length; ++i ) + if ( !implExecuteStatement( sInsertionPrefix + aValues[ i ] + ")" ) ) + { + failed( "could not create the required sample data" ); + return false; + } + + connection.refreshTables(); + + // do not need the connection anymore + connection.close(); + + return true; + } + + /* ------------------------------------------------------------------ */ + /// checks the 3 radio buttons for the given states + private boolean checkRadios( short stateNone, short stateNormal, short stateImportant ) throws com.sun.star.uno.Exception, java.lang.Exception + { + if ( ((Short)getRadioModel( "radio_group", "none" ).getPropertyValue( "State" )).shortValue() != stateNone ) + { + failed( "wrong value of the 'none' radio button!" ); + } + else if ( ((Short)getRadioModel( "radio_group", "normal" ).getPropertyValue( "State" )).shortValue() != stateNormal ) + { + failed( "wrong value of the 'normal' radio button!" ); + } + else if ( ((Short)getRadioModel( "radio_group", "important" ).getPropertyValue( "State" )).shortValue() != stateImportant ) + { + failed( "wrong value of the 'important' radio button!" ); + } + else + return true; + + return false; + } + + /* ------------------------------------------------------------------ */ + private boolean checkNullValue( String fieldName, String propertyName ) throws com.sun.star.uno.Exception, java.lang.Exception + { + Object value = getControlModel( fieldName ).getPropertyValue( propertyName ); + if ( !util.utils.isVoid( value ) ) + { + log.println( "wrong value of the " + fieldName + " field!" ); + log.println( " expected: <null/>" ); + log.println( " found : " + value.toString() ); + } + else + return true; + + return false; + } + + /* ------------------------------------------------------------------ */ + private boolean checkIntValue( int requiredValue, String fieldName, String propertyName ) throws com.sun.star.uno.Exception, java.lang.Exception + { + try + { + if ( "f_time".equals(fieldName) ) + // http://bugs.mysql.com/bug.php?id=5681 + return true; + if (fieldName == null) { + return false; + } + int currentValue = ((Integer)getControlModel( fieldName ).getPropertyValue( propertyName )).intValue(); + if ( currentValue != requiredValue ) + { + log.println( "wrong value of the " + fieldName + " field!" ); + log.println( " expected: " + requiredValue ); + log.println( " found : " + currentValue ); + } + else + return true; + } + catch( com.sun.star.uno.Exception e ) + { + log.println( "caught an exception while retrieving property value '" + propertyName + "' of control model '" + fieldName + "'" ); + throw e; + } + + return false; + } + + /* ------------------------------------------------------------------ */ + private boolean checkShortValue( short requiredValue, String fieldName, String propertyName ) throws com.sun.star.uno.Exception, java.lang.Exception + { + try + { + short currentValue = ((Short)getControlModel( fieldName ).getPropertyValue( propertyName )).shortValue(); + if ( currentValue != requiredValue ) + { + log.println( "wrong value of the " + fieldName + " field!" ); + log.println( " expected: " + requiredValue ); + log.println( " found : " + currentValue ); + } + else + return true; + } + catch( com.sun.star.uno.Exception e ) + { + log.println( "caught an exception while retrieving property value '" + propertyName + "' of control model '" + fieldName + "'" ); + throw e; + } + + return false; + } + + /* ------------------------------------------------------------------ */ + private boolean checkDoubleValue( double requiredValue, String fieldName, String propertyName ) throws com.sun.star.uno.Exception, java.lang.Exception + { + double currentValue = ((Double)getControlModel( fieldName ).getPropertyValue( propertyName )).doubleValue(); + if ( currentValue != requiredValue ) + { + log.println( "wrong value of the " + fieldName + " field!" ); + log.println( " expected: " + requiredValue ); + log.println( " found : " + currentValue ); + } + else + return true; + + return false; + } + + /* ------------------------------------------------------------------ */ + private boolean checkStringValue( String requiredValue, String fieldName, String propertyName ) throws com.sun.star.uno.Exception, java.lang.Exception + { + String currentValue = (String)getControlModel( fieldName ).getPropertyValue( propertyName ); + if ( !currentValue.equals( requiredValue ) ) + { + log.println( "wrong value of the " + fieldName + " field!" ); + log.println( " expected: " + requiredValue ); + log.println( " found : " + currentValue ); + } + else + return true; + + return false; + } + + /* ------------------------------------------------------------------ */ + private XPropertySet getRadioModel( String name, String refValue ) throws com.sun.star.uno.Exception, java.lang.Exception + { + return m_formLayer.getRadioModelByRefValue( m_masterForm, name, refValue ); + } + + /* ------------------------------------------------------------------ */ + /** executes the given statement on the given connection + */ + protected boolean implExecuteStatement( String sStatement ) throws java.lang.Exception + { + try + { + m_databaseDocument.executeSQL( sStatement ); + } + catch(com.sun.star.sdbc.SQLException e) + { + System.err.println( e ); + return false; + } + + return true; + } + + /* ------------------------------------------------------------------ */ + /** simulates a user's text input into a control given by model name + */ + private void userTextInput( String modelName, String text, boolean withCommit ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet controlModel = getControlModel( modelName ); + // the form runtime environment (namely the form controller) rely on focus events for recognizing + // control content changes ... + if ( withCommit ) + m_document.getCurrentView().grabControlFocus( controlModel ); + + m_formLayer.userTextInput( controlModel, text ); + + // focus back to a dummy control model so the content of the model we just changed will + // be committed to the underlying database column + if ( withCommit ) + m_document.getCurrentView().grabControlFocus( getControlModel( "dummy" ) ); + } + + /* ------------------------------------------------------------------ */ + private void moveToInsertRow() throws com.sun.star.uno.Exception, java.lang.Exception + { + XResultSetUpdate xResultSet = UnoRuntime.queryInterface( XResultSetUpdate.class, m_masterForm ); + xResultSet.moveToInsertRow( ); + } + + /* ------------------------------------------------------------------ */ + private void moveToFirst() throws com.sun.star.uno.Exception, java.lang.Exception + { + XResultSet xResultSet = UnoRuntime.queryInterface( XResultSet.class, m_masterForm ); + xResultSet.first( ); + } + + /* ------------------------------------------------------------------ */ + private void moveToNext() throws com.sun.star.uno.Exception, java.lang.Exception + { + XResultSet xResultSet = UnoRuntime.queryInterface( XResultSet.class, m_masterForm ); + xResultSet.next( ); + } + + /* ------------------------------------------------------------------ */ + /** simulates pressing a toolbox button with the given URL + */ + private void executeSlot( String slotURL ) throws java.lang.Exception + { + XDispatch xDispatch = m_document.getCurrentView().getDispatcher( slotURL ); + + URL[] url = new URL[] { new URL() }; + url[0].Complete = slotURL; + XURLTransformer xTransformer = UnoRuntime.queryInterface( + XURLTransformer.class, m_orb.createInstance( "com.sun.star.util.URLTransformer" ) ); + xTransformer.parseStrict( url ); + + PropertyValue[] aArgs = new PropertyValue[0]; + xDispatch.dispatch( url[0], aArgs ); + } + + /* ------------------------------------------------------------------ */ + /** undos the changes on the current record, by simulating pressing of the respective toolbox button + */ + private void undoRecordByUI() throws java.lang.Exception + { + executeSlot( ".uno:RecUndo" ); + } + + /* ------------------------------------------------------------------ */ + /** saves the current record, by simulating pressing of the respective toolbox button + */ + private void saveRecordByUI() throws java.lang.Exception + { + executeSlot( ".uno:RecSave" ); + } + + /* ------------------------------------------------------------------ */ + /** moves to the next record, by simulating pressing of the respective toolbox button + */ + private void nextRecordByUI() throws java.lang.Exception + { + executeSlot( ".uno:NextRecord" ); + } + /* ------------------------------------------------------------------ */ + /** moves to the previous record, by simulating pressing of the respective toolbox button + */ + private void previousRecordByUI() throws java.lang.Exception + { + executeSlot( ".uno:PrevRecord" ); + } + + /* ------------------------------------------------------------------ */ + private void createImageFile() throws java.io.IOException + { + m_sImageURL = util.utils.getOfficeTempDir( m_orb ) + "image.gif"; + + FileOutputStream aFile = null; + try + { + aFile = new FileOutputStream( m_sImageURL ); + aFile.write( getSamplePicture() ); + } + finally + { + if ( aFile != null ) + aFile.close(); + } + log.println( "created temporary image file: " + m_sImageURL ); + + // for later setting the url at the image control, we need a real URL, no system path + m_sImageURL = util.utils.getOfficeTemp( m_orb ) + "image.gif"; + } + + /* ------------------------------------------------------------------ */ + private byte[] getSamplePicture() + { + byte[] aBytes = new byte[] { + (byte)0x47, (byte)0x49, (byte)0x46, (byte)0x38, (byte)0x39, (byte)0x61, (byte)0x0A, (byte)0x00, (byte)0x0A, (byte)0x00, (byte)0xB3, (byte)0x00, (byte)0x00, (byte)0xFF, (byte)0x00, (byte)0x00, + (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0xFF, (byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, + (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0x2C, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x0A, (byte)0x00, (byte)0x0A, (byte)0x00, (byte)0x00, (byte)0x04, (byte)0x20, (byte)0x10, (byte)0xC8, (byte)0x49, (byte)0x41, (byte)0xB9, (byte)0xF8, (byte)0xCA, + (byte)0x12, (byte)0xBA, (byte)0x2F, (byte)0x5B, (byte)0x30, (byte)0x8C, (byte)0x43, (byte)0x00, (byte)0x5A, (byte)0x22, (byte)0x41, (byte)0x94, (byte)0x27, (byte)0x37, (byte)0xA8, (byte)0x6C, + (byte)0x48, (byte)0xC6, (byte)0xA8, (byte)0xD7, (byte)0xB5, (byte)0x19, (byte)0x56, (byte)0xED, (byte)0x11, (byte)0x00, (byte)0x3B + }; + + return aBytes; + } + + /* ------------------------------------------------------------------ */ + private byte[] getSamplePictureBytes() + { + byte[] aBytes = new byte[] { + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, + (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x05, + (byte)0x01, (byte)0x03, (byte)0x03, (byte)0x03, (byte)0x03, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x01, (byte)0x03, (byte)0x04, (byte)0x04, (byte)0x03, (byte)0x01, + (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x01, (byte)0x03, (byte)0x04, (byte)0x04, (byte)0x03, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x01, (byte)0x03, + (byte)0x03, (byte)0x03, (byte)0x03, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x05, (byte)0x00, + (byte)0x00, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, + (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 + }; + return aBytes; + } + + /* ------------------------------------------------------------------ */ + private boolean verifyReferenceImage( byte[] referenceBytes ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet xImageModel = getControlModel( "f_blob" ); + + // check if the image control properly says that there currently is no image on the first record + XImageProducerSupplier xSuppProducer = UnoRuntime.queryInterface( XImageProducerSupplier.class, + xImageModel ); + XImageProducer xProducer = xSuppProducer.getImageProducer(); + + ImageComparison compareImages = new ImageComparison( referenceBytes, this ); + synchronized( this ) + { + xProducer.addConsumer( compareImages ); + xProducer.startProduction(); + } + xProducer.removeConsumer( compareImages ); + + return compareImages.imagesEqual( ); + } + + /* ------------------------------------------------------------------ */ + public void errorOccured( SQLErrorEvent _event ) + { + // just remember for the moment + m_mostRecentErrorEvent = _event; + } + + /* ------------------------------------------------------------------ */ + public void disposing( EventObject _event ) + { + // not interested in + } +} diff --git a/forms/qa/integration/forms/FormLayer.java b/forms/qa/integration/forms/FormLayer.java new file mode 100644 index 000000000..3d28ff054 --- /dev/null +++ b/forms/qa/integration/forms/FormLayer.java @@ -0,0 +1,318 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.accessibility.XAccessible; +import com.sun.star.accessibility.XAccessibleEditableText; +import com.sun.star.uno.UnoRuntime; + +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexContainer; +import com.sun.star.container.XIndexAccess; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.drawing.XControlShape; +import com.sun.star.drawing.XShapes; +import com.sun.star.awt.Size; +import com.sun.star.awt.Point; +import com.sun.star.awt.VisualEffect; +import com.sun.star.awt.XControlModel; +import com.sun.star.text.TextContentAnchorType; +import com.sun.star.drawing.XDrawPage; + +public class FormLayer +{ + private final DocumentHelper m_document; + private XDrawPage m_page; + + /* ------------------------------------------------------------------ */ + /** Creates a new instance of FormLayer */ + public FormLayer( DocumentHelper _document ) + { + m_document = _document; + } + + /* ------------------------------------------------------------------ */ + /** sets the page which is to be used for subsequent insertions of controls/shapes + */ + void setInsertPage( int page ) throws com.sun.star.lang.IndexOutOfBoundsException, com.sun.star.lang.WrappedTargetException + { + m_page = m_document.getDrawPage( page ); + } + + /* ------------------------------------------------------------------ */ + /** creates a control in the document + + <p>Note that <em>control<em> here is an incorrect terminology. What the method really does is + it creates a control shape, together with a control model, and inserts them into the document model. + This will result in every view to this document creating a control described by the model-shape pair. + </p> + + @param sFormComponentService + the service name of the form component to create, e.g. "TextField" + @param nXPos + the abscissa of the position of the newly inserted shape + @param nWidth + the width of the newly inserted shape + @param nHeight + the height of the newly inserted shape + @param _parentForm + the form to use as parent for the newly create form component. May be null, in this case + a default parent is chosen by the implementation + @return + the property access to the control's model + */ + public XPropertySet createControlAndShape( String sFormComponentService, int nXPos, + int nYPos, int nWidth, int nHeight, Object _parentForm ) throws java.lang.Exception + { + // let the document create a shape + XMultiServiceFactory xDocAsFactory = UnoRuntime.queryInterface( + XMultiServiceFactory.class, m_document.getDocument() ); + XControlShape xShape = UnoRuntime.queryInterface( XControlShape.class, + xDocAsFactory.createInstance( "com.sun.star.drawing.ControlShape" ) ); + + // position and size of the shape + xShape.setSize( new Size( nWidth * 100, nHeight * 100 ) ); + xShape.setPosition( new Point( nXPos * 100, nYPos * 100 ) ); + + // adjust the anchor so that the control is tied to the page + XPropertySet xShapeProps = dbfTools.queryPropertySet( xShape ); + TextContentAnchorType eAnchorType = TextContentAnchorType.AT_PARAGRAPH; + xShapeProps.setPropertyValue( "AnchorType", eAnchorType ); + + // create the form component (the model of a form control) + String sQualifiedComponentName = "com.sun.star.form.component." + sFormComponentService; + XControlModel xModel = UnoRuntime.queryInterface( XControlModel.class, + m_document.getOrb().createInstance( sQualifiedComponentName ) ); + + // insert the model into the form component hierarchy, if the caller gave us a location + if ( null != _parentForm ) + { + XIndexContainer parentForm; + if ( _parentForm instanceof XIndexContainer ) + parentForm = (XIndexContainer)_parentForm; + else + parentForm = UnoRuntime.queryInterface( XIndexContainer.class, _parentForm ); + parentForm.insertByIndex( parentForm.getCount(), xModel ); + } + + // knitt them + xShape.setControl( xModel ); + + // add the shape to the shapes collection of the document + XDrawPage pageWhereToInsert = ( m_page != null ) ? m_page : m_document.getMainDrawPage(); + + XShapes xDocShapes = UnoRuntime.queryInterface( XShapes.class, pageWhereToInsert ); + xDocShapes.add( xShape ); + + // and outta here with the XPropertySet interface of the model + XPropertySet xModelProps = dbfTools.queryPropertySet( xModel ); + return xModelProps; + } + + /* ------------------------------------------------------------------ */ + /** creates a control in the document + + <p>Note that <em>control<em> here is an incorrect terminology. What the method really does is + it creates a control shape, together with a control model, and inserts them into the document model. + This will result in every view to this document creating a control described by the model-shape pair. + </p> + + @param sFormComponentService + the service name of the form component to create, e.g. "TextField" + @param nXPos + the abscissa of the position of the newly inserted shape + @param nWidth + the width of the newly inserted shape + @param nHeight + the height of the newly inserted shape + @return + the property access to the control's model + */ + public XPropertySet createControlAndShape( String sFormComponentService, int nXPos, + int nYPos, int nWidth, int nHeight ) throws java.lang.Exception + { + return createControlAndShape( sFormComponentService, nXPos, nYPos, nWidth, nHeight, null ); + } + + /** creates a pair of controls, namely a label control, and another control labeled by it + * + * @param _formComponentServiceName + * the service name for the control which is not the label control + * @param _label + * the label to be shown in the label control + * @param _xPos + * the horizontal position of the control pair + * @param _yPos + * the vertical position of the control pair + * @param _height + * the height of the control which is not the label control + * @return + * the model of the control which is not the label control + */ + public XPropertySet createLabeledControl( String _formComponentServiceName, String _label, int _xPos, + int _yPos, int _height ) + throws java.lang.Exception + { + // insert the label control + XPropertySet label = createControlAndShape( "FixedText", _xPos, _yPos, 25, 6 ); + label.setPropertyValue( "Label", _label ); + + // insert the text field control + XPropertySet field = createControlAndShape( _formComponentServiceName, + _xPos + 25, _yPos, 40, _height ); + // knit it to its label component + field.setPropertyValue( "LabelControl", label ); + + // names + label.setPropertyValue( "Name", _label + "_Label" ); + field.setPropertyValue( "Name", _label ); + + return field; + } + + /* ------------------------------------------------------------------ */ + /** creates a line of controls, consisting of a label and a field for data input. + + <p>In opposite to the second form of this method, here the height of the field, + as well as the abscissa of the label, are under the control of the caller.</p> + + @param sControlType + specifies the type of the data input control + @param sFieldName + specifies the field name the text field should be bound to + @param _controlNamePostfix + specifies a postfix to append to the logical control names + @param nYPos + specifies the Y position of the line to start at + @param nHeight + the height of the field + @return + the control model of the created data input field + */ + public XPropertySet insertControlLine( String sControlType, String sFieldName, String _controlNamePostfix, + int nXPos, int nYPos, int nHeight ) + throws java.lang.Exception + { + // insert the label control + XPropertySet xLabelModel = createControlAndShape( "FixedText", nXPos, nYPos, 25, 6 ); + xLabelModel.setPropertyValue( "Label", sFieldName ); + + // insert the text field control + XPropertySet xFieldModel = createControlAndShape( sControlType, nXPos + 26, nYPos, 40, nHeight ); + xFieldModel.setPropertyValue( "DataField", sFieldName ); + if ( xFieldModel.getPropertySetInfo().hasPropertyByName( "Border" ) ) + { + xFieldModel.setPropertyValue( "Border", Short.valueOf( VisualEffect.FLAT ) ); + if ( xFieldModel.getPropertySetInfo().hasPropertyByName( "BorderColor" ) ) + xFieldModel.setPropertyValue( "BorderColor", Integer.valueOf( 0x00C0C0C0 ) ); + } + // knit it to its label component + xFieldModel.setPropertyValue( "LabelControl", xLabelModel ); + + // some names, so later on we can find them + if ( _controlNamePostfix == null ) + _controlNamePostfix = ""; + xLabelModel.setPropertyValue( "Name", sFieldName + _controlNamePostfix + "_Label" ); + xFieldModel.setPropertyValue( "Name", sFieldName + _controlNamePostfix ); + + return xFieldModel; + } + + /* ------------------------------------------------------------------ */ + /** creates a line of controls, consisting of a label and a field for data input. + + @param sControlType + specifies the type of the data input control + @param sFieldName + specifies the field name the text field should be bound to + @param nYPos + specifies the Y position of the line to start at + @return + the control model of the created data input field + */ + public XPropertySet insertControlLine( String sControlType, String sFieldName, String sControlNamePostfix, int nYPos ) + throws java.lang.Exception + { + return insertControlLine( sControlType, sFieldName, sControlNamePostfix, 10, nYPos, 6 ); + } + + /* ------------------------------------------------------------------ */ + /** retrieves the radio button model with the given name and the given ref value + * @param form + * the parent form of the radio button model to find + * @param name + * the name of the radio button + * @param refValue + * the reference value of the radio button + */ + public XPropertySet getRadioModelByRefValue( XPropertySet form, String name, String refValue ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XIndexAccess indexAccess = UnoRuntime.queryInterface( XIndexAccess.class, form ); + + for ( int i=0; i<indexAccess.getCount(); ++i ) + { + XPropertySet control = dbfTools.queryPropertySet( indexAccess.getByIndex( i ) ); + + if ( ((String)control.getPropertyValue( "Name" )).equals( name ) ) + if ( ((String)control.getPropertyValue( "RefValue" )).equals( refValue ) ) + return control; + } + return null; + } + + + + /* ------------------------------------------------------------------ */ + /** retrieves a control model with a given (integer) access path + */ + public XPropertySet getControlModel( int[] _accessPath ) throws com.sun.star.uno.Exception + { + XIndexAccess indexAcc = UnoRuntime.queryInterface( XIndexAccess.class, + m_document.getFormComponentTreeRoot() ); + XPropertySet controlModel = null; + int i=0; + while ( ( indexAcc != null ) && ( i < _accessPath.length ) ) + { + controlModel = UnoRuntime.queryInterface( XPropertySet.class, + indexAcc.getByIndex( _accessPath[i] ) ); + indexAcc = UnoRuntime.queryInterface( XIndexAccess.class, + controlModel ); + ++i; + } + return controlModel; + } + + + + /* ------------------------------------------------------------------ */ + /** simulates a user's text input into a control given by control model + */ + public void userTextInput( XPropertySet controlModel, String text ) throws com.sun.star.uno.Exception, java.lang.Exception + { + // we will *not* simply set the value property at the model. This is not the same as + // doing a user input, as the latter will trigger a lot of notifications, which the forms runtime environment + // (namely the FormController) relies on to notice that the control changed. + // Instead, we use the Accessibility interfaces of the control to simulate text input + XAccessible formattedAccessible = UnoRuntime.queryInterface( XAccessible.class, + m_document.getCurrentView().getControl( controlModel ) + ); + XAccessibleEditableText textAccess = UnoRuntime.queryInterface( XAccessibleEditableText.class, + formattedAccessible.getAccessibleContext() ); + textAccess.setText( text ); + } +} diff --git a/forms/qa/integration/forms/FormPropertyBags.java b/forms/qa/integration/forms/FormPropertyBags.java new file mode 100644 index 000000000..61aa984c1 --- /dev/null +++ b/forms/qa/integration/forms/FormPropertyBags.java @@ -0,0 +1,201 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.beans.PropertyAttribute; +import com.sun.star.beans.PropertyChangeEvent; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XPropertyChangeListener; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertySetInfo; +import com.sun.star.frame.XStorable; +import com.sun.star.lang.EventObject; +import com.sun.star.uno.UnoRuntime; + +import com.sun.star.lang.XMultiServiceFactory; + +import com.sun.star.util.XCloseable; + +public class FormPropertyBags extends complexlib.ComplexTestCase implements XPropertyChangeListener +{ + private DocumentHelper m_document; + private FormLayer m_formLayer; + private XMultiServiceFactory m_orb; + + private PropertyChangeEvent m_propertyChangeEvent; + + /** Creates a new instance of FormPropertyBags */ + public FormPropertyBags() + { + m_propertyChangeEvent = new PropertyChangeEvent(); + } + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkSomething" + }; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Form Component Property Bag Test"; + } + + /* ------------------------------------------------------------------ */ + public void before() throws com.sun.star.uno.Exception, java.lang.Exception + { + m_orb = param.getMSF(); + m_document = DocumentHelper.blankTextDocument( m_orb ); + m_formLayer = new FormLayer( m_document ); + } + + /* ------------------------------------------------------------------ */ + private void impl_closeDoc() throws com.sun.star.uno.Exception + { + if ( m_document != null ) + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, m_document.getDocument() ); + closeDoc.close( true ); + } + } + + /* ------------------------------------------------------------------ */ + public void after() throws com.sun.star.uno.Exception, java.lang.Exception + { + impl_closeDoc(); + } + + /* ------------------------------------------------------------------ */ + public void checkSomething() throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet textFieldModel = m_formLayer.createControlAndShape( "DatabaseTextField", 10, 10, 25, 6 ); + + // check whether adding new properties is successful + XPropertyContainer propContainer = UnoRuntime.queryInterface( + XPropertyContainer.class, textFieldModel ); + assure("XPropertyContainer not supported!", propContainer != null ); + + propContainer.addProperty( "SomeBoundText", PropertyAttribute.BOUND, "InitialBoundText" ); + propContainer.addProperty( "SomeTransientText", PropertyAttribute.TRANSIENT, "InitialTransientProperty" ); + propContainer.addProperty( "SomeReadonlyText", PropertyAttribute.READONLY, "InitialReadonlyText" ); + propContainer.addProperty( "SomeNumericValue", PropertyAttribute.BOUND, Integer.valueOf( 42 ) ); + + XPropertySetInfo propertyInfo = textFieldModel.getPropertySetInfo(); + assure( "Per service definition, dynamic properties are expected to be forced to be removable", + ( propertyInfo.getPropertyByName("SomeBoundText").Attributes & PropertyAttribute.REMOVABLE ) != 0 ); + + // a second addition of a property with an existent name should be rejected + boolean caughtExpected = false; + try { propContainer.addProperty( "SomeBoundText", PropertyAttribute.BOUND, "InitialBoundText" ); } + catch( PropertyExistException e ) { caughtExpected = true; } + catch( Exception e ) { } + assure( "repeated additions of a property with the same name should be rejected", + caughtExpected ); + + // check whether the properties are bound as expected + impl_checkPropertyValueNotification( textFieldModel ); + + // check property value persistence + impl_checkPropertyPersistence(); + } + + /* ------------------------------------------------------------------ */ + private void impl_checkPropertyValueNotification( XPropertySet _controlModel ) throws com.sun.star.uno.Exception + { + _controlModel.addPropertyChangeListener( "", this ); + + _controlModel.setPropertyValue( "SomeBoundText", "ChangedBoundText" ); + assure( "changes in the bound property are not properly notified", + m_propertyChangeEvent.PropertyName.equals( "SomeBoundText" ) + && m_propertyChangeEvent.OldValue.equals( "InitialBoundText" ) + && m_propertyChangeEvent.NewValue.equals( "ChangedBoundText" ) ); + + m_propertyChangeEvent = null; + _controlModel.setPropertyValue( "SomeTransientText", "ChangedTransientText" ); + assure( "changes in non-bound properties should not be notified", + m_propertyChangeEvent == null ); + + boolean caughtExpected = false; + try { _controlModel.setPropertyValue( "SomeReadonlyText", "ChangedReadonlyText" ); } + catch( PropertyVetoException e ) { caughtExpected = true; } + catch( Exception e ) { } + assure( "trying to write a read-only property did not give the expected result", + caughtExpected ); + + _controlModel.removePropertyChangeListener( "", this ); + } + + /* ------------------------------------------------------------------ */ + private void impl_checkPropertyPersistence() throws com.sun.star.uno.Exception + { + // store the document + XStorable store = UnoRuntime.queryInterface( XStorable.class, m_document.getDocument() ); + String documentURL = util.utils.getOfficeTemp( m_orb ) + "document.odt"; + PropertyValue[] storeArguments = new PropertyValue[] { new PropertyValue() }; + storeArguments[0].Name = "FilterName"; + storeArguments[0].Value = "writer8"; + store.storeAsURL( documentURL, storeArguments ); + + // close and re-load it + impl_closeDoc(); + + m_document = DocumentHelper.loadDocument( m_orb, documentURL ); + m_formLayer = new FormLayer( m_document ); + + XPropertySet textFieldModel = m_formLayer.getControlModel( new int[] { 0, 0 } ); + + // all persistent properties should have the expected values + assure( "persistent properties did not survive reload (1)!", ((String)textFieldModel.getPropertyValue( "SomeBoundText" )).equals( "ChangedBoundText" ) ); + assure( "persistent properties did not survive reload (2)!", ((String)textFieldModel.getPropertyValue( "SomeReadonlyText" )).equals( "InitialReadonlyText" ) ); +// assure( "persistent properties did not survive reload (3)!", ((Integer)textFieldModel.getPropertyValue( "SomeNumericValue" )).equals( Integer.valueOf( 42 ) ) ); + // cannot check this until the types really survive - at the moment, integers are converted to doubles... + + // the transient property should not have survived + boolean caughtExpected = false; + try { textFieldModel.getPropertyValue( "SomeTransientText" ); } + catch( UnknownPropertyException e ) { caughtExpected = true; } + assure( "transient property did survive reload!", caughtExpected ); + + // There would be more things to check. + // For instance, it would be desirable of the property attributes would have survived + // the reload, and the property defaults (XPropertyState). + // However, the file format currently doesn't allow for this, so those information + // is lost when saving the document. + } + + /* ------------------------------------------------------------------ */ + public void propertyChange(PropertyChangeEvent _propertyChangeEvent) + { + m_propertyChangeEvent = _propertyChangeEvent; + } + + /* ------------------------------------------------------------------ */ + public void disposing(EventObject eventObject) + { + // not interested in + } +} diff --git a/forms/qa/integration/forms/ImageComparison.java b/forms/qa/integration/forms/ImageComparison.java new file mode 100644 index 000000000..e28771fc7 --- /dev/null +++ b/forms/qa/integration/forms/ImageComparison.java @@ -0,0 +1,79 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +public final class ImageComparison implements com.sun.star.awt.XImageConsumer +{ + + private final byte[] m_referenceBytes; + private int m_referencePosition; + private final Object m_notifyDone; + + public boolean imagesEqual( ) + { + return m_referencePosition == m_referenceBytes.length; + } + + /** Creates a new instance of ImageComparison */ + public ImageComparison( byte[] referenceBytes, Object toNotify ) + { + m_referenceBytes = referenceBytes; + m_referencePosition = 0; + m_notifyDone = toNotify; + } + + public void complete(int param, com.sun.star.awt.XImageProducer xImageProducer) + { + synchronized( m_notifyDone ) + { + m_notifyDone.notify(); + } + } + + public void init(int param, int param1) + { + } + + public void setColorModel(short param, int[] values, int param2, int param3, int param4, int param5) + { + } + + public void setPixelsByBytes(int param, int param1, int param2, int param3, byte[] values, int param5, int param6) + { + if ( m_referencePosition == -1 ) + // already failed + return; + + int i = 0; + while ( ( m_referencePosition < m_referenceBytes.length ) && ( i < values.length ) ) + { + if ( m_referenceBytes[ m_referencePosition ] != values[ i ] ) + { + m_referencePosition = -1; + break; + } + ++i; + ++m_referencePosition; + } + } + + public void setPixelsByLongs(int param, int param1, int param2, int param3, int[] values, int param5, int param6) + { + } + +} diff --git a/forms/qa/integration/forms/ListBox.java b/forms/qa/integration/forms/ListBox.java new file mode 100644 index 000000000..fc60b4837 --- /dev/null +++ b/forms/qa/integration/forms/ListBox.java @@ -0,0 +1,285 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.sun.star.awt.XListBox; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XChild; +import com.sun.star.container.XIndexAccess; +import com.sun.star.container.XNameAccess; +import com.sun.star.form.ListSourceType; +import com.sun.star.form.runtime.FormFeature; +import com.sun.star.form.runtime.XFormController; +import com.sun.star.form.runtime.XFormOperations; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdbc.XParameters; +import com.sun.star.sdbc.XPreparedStatement; +import com.sun.star.uno.Exception; +import com.sun.star.uno.UnoRuntime; + +import connectivity.tools.HsqlColumnDescriptor; +import connectivity.tools.HsqlDatabase; +import connectivity.tools.HsqlTableDescriptor; +import connectivity.tools.sdb.Connection; + +public class ListBox extends TestCase +{ + HsqlDatabase m_database = null; + private static final String m_foreignKeyTableName = "foreign_keys"; + + public ListBox() + { + super( DocumentType.WRITER ); + } + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkForeignKeys" + }; + } + + /* ------------------------------------------------------------------ */ + public void checkForeignKeys() throws com.sun.star.uno.Exception, java.lang.Exception + { + try + { + // create the form document + prepareDocument(); + + final XIndexAccess formsCollection = UnoRuntime.queryInterface( XIndexAccess.class, + m_document.getFormComponentTreeRoot() ); + final XNameAccess form = UnoRuntime.queryInterface( XNameAccess.class, formsCollection.getByIndex(0) ); + + final DocumentViewHelper view = m_document.getCurrentView(); + final XFormController formController = view.getFormController( form ); + final XFormOperations formOperations = formController.getFormOperations(); + + // move through all records, and check that the display values in the list boxes are as expected + final String[][] fieldTypesDefinitions = impl_getFieldTypeDefinitions(); + final String[] fieldTypes = fieldTypesDefinitions[0]; + + final String[] displayValues = impl_getDisplayValues(); + + formOperations.execute( FormFeature.MoveToFirst ); + for ( int row=0; row<2; ++row ) + { + StringBuilder failedFieldTypes = new StringBuilder(); + for ( int i=0; i<fieldTypes.length; ++i ) + { + final String columnFKName = fieldTypes[i] + "_fk"; + Object listBoxModel = form.getByName( columnFKName ); + XListBox listBoxControl = UnoRuntime.queryInterface( XListBox.class, + view.getControl( listBoxModel ) ); + if ( !listBoxControl.getSelectedItem().equals( displayValues[row] ) ) + { + if ( failedFieldTypes.length() > 0 ) + failedFieldTypes.append( ", " ); + failedFieldTypes.append( fieldTypes[i] ); + } + } + /*assure( "The following field types do not work when used as bound list box fields: " + failedFieldTypes.toString() + + " (row " + row + ")", failedFieldTypes.length() == 0 );*/ + + formOperations.execute( FormFeature.MoveToNext ); + } + + } + finally + { + closeDocument(); + } + } + + /* ------------------------------------------------------------------ */ + @Override + public void before() throws Exception, java.lang.Exception + { + super.before(); + impl_createDatabase(); + } + + /* ------------------------------------------------------------------ */ + @Override + protected void prepareDocument() throws com.sun.star.uno.Exception, java.lang.Exception + { + super.prepareDocument(); + impl_createForm(); + } + + /* ------------------------------------------------------------------ */ + private String[][] impl_getFieldTypeDefinitions() + { + return new String[][] { + new String[] { + "bigint", "boolean", "date", "decimal", "double", "float", "numeric", "time", "timestamp", "tinyint", "varchar" + }, + new String[] { + null, null, null, "(10,2)", null, null, "(10,2)", null, null, null, "(50)" + } + }; + } + + /* ------------------------------------------------------------------ */ + private String[] impl_getTypedValue( final String _asType ) + { + Map< String, String[] > valueMap = new HashMap< String, String[] >(); + valueMap.put( "bigint", new String[] { "1111111111", "222222222" } ); + valueMap.put( "boolean", new String[] { "false", "true" } ); + valueMap.put( "date", new String[] { "2001-01-01", "2002-02-02" } ); + valueMap.put( "decimal", new String[] { "1.11", "2.22" } ); + valueMap.put( "double", new String[] { "1.11", "2.22" } ); + valueMap.put( "float", new String[] { "1.11", "2.22" } ); + valueMap.put( "numeric", new String[] { "1.11", "2.22" } ); + valueMap.put( "time", new String[] { "01:01:01", "02:02:02" } ); + valueMap.put( "timestamp", new String[] { "2001-01-01 01:01:01", "2002-02-02 02:02:02" } ); + valueMap.put( "tinyint", new String[] { "1", "2" } ); + valueMap.put( "varchar", new String[] { "first", "second" } ); + + return valueMap.get( _asType ); + } + + /* ------------------------------------------------------------------ */ + private String[] impl_getDisplayValues() + { + return new String[] { "one", "two" }; + } + + /* ------------------------------------------------------------------ */ + private void impl_createDatabase() throws java.lang.Exception + { + try + { + m_database = new HsqlDatabase( m_orb ); + Connection connection = m_database.defaultConnection(); + System.out.println( m_database.getDocumentURL() ); + + final String[][] fieldDefinitions = impl_getFieldTypeDefinitions(); + final String[] keyTypes = fieldDefinitions[0]; + final String[] keyCreationArgs = fieldDefinitions[1]; + + ArrayList< HsqlColumnDescriptor > foreignKeyColumns = new ArrayList< HsqlColumnDescriptor >(); + foreignKeyColumns.add( new HsqlColumnDescriptor( "ID", "integer", HsqlColumnDescriptor.PRIMARY ) ); + + ArrayList< String[] > foreignKeyValues = new ArrayList< String[] >(); + + StringBuilder foreignKeyInsertSQL = new StringBuilder(); + foreignKeyInsertSQL.append( "INSERT INTO \"" + m_foreignKeyTableName + "\" VALUES (?" ); + + final String[] displayValues = impl_getDisplayValues(); + + for ( int i=0; i<keyTypes.length; ++i ) + { + final String tableName = keyTypes[i] + "_pk"; + final String columnPKName = keyTypes[i] + "_pk"; + final String columnFKName = keyTypes[i] + "_fk"; + final String columnType = keyTypes[i] + ( keyCreationArgs[i] != null ? keyCreationArgs[i] : "" ); + m_database.createTable( new HsqlTableDescriptor( tableName, + new HsqlColumnDescriptor[] { + new HsqlColumnDescriptor( columnPKName, columnType, HsqlColumnDescriptor.PRIMARY ), + new HsqlColumnDescriptor( "content", "varchar(50)" ) + } + ) ); + + // insert a few rows + StringBuilder sql = new StringBuilder(); + sql.append( "INSERT INTO \"" ); + sql.append( tableName ); + sql.append( "\" VALUES (?, ?)"); + XPreparedStatement statement = connection.prepareStatement( sql.toString() ); + XParameters statementParameters = UnoRuntime.queryInterface( XParameters.class, statement ); + + final String[] keyValues = impl_getTypedValue( keyTypes[i] ); + + for ( int row=0; row<displayValues.length; ++row ) + { + statementParameters.setString( 1, keyValues[row] ); + statementParameters.setString( 2, displayValues[row] ); + statement.execute(); + } + + // remember a column descriptor for later creation of the table with the foreign keys + foreignKeyColumns.add( new HsqlColumnDescriptor( columnFKName, columnType, HsqlColumnDescriptor.REQUIRED, + tableName, columnPKName ) ); + + // remember the data to fill into this table + foreignKeyValues.add( keyValues ); + foreignKeyInsertSQL.append( ", ?" ); + } + + // create the table taking all those foreign keys + m_database.createTable( new HsqlTableDescriptor( m_foreignKeyTableName, foreignKeyColumns.toArray( new HsqlColumnDescriptor[foreignKeyColumns.size()] ) ) ); + // fill in some data + foreignKeyInsertSQL.append( ")" ); + XPreparedStatement statement = connection.prepareStatement( foreignKeyInsertSQL.toString() ); + XParameters statementParameters = UnoRuntime.queryInterface( XParameters.class, statement ); + for ( int row=0; row<2; ++row ) + { + statementParameters.setInt( 1, row ); + for ( int i=0; i<keyTypes.length; ++i ) + { + statementParameters.setString( i+2, foreignKeyValues.get(i)[row] ); + } + statement.execute(); + } + + m_database.defaultConnection().refreshTables(); + } + finally + { + if ( m_database != null ) + m_database.store(); + } + } + + /* ------------------------------------------------------------------ */ + private void impl_createForm() throws java.lang.Exception + { + // a single control for the ID field + XPropertySet controlModel = m_formLayer.insertControlLine( "DatabaseNumericField", "ID", null, 10, 10, 6 ); + // bind the form to the foreign_keys table + XPropertySet form = dbfTools.queryPropertySet( dbfTools.getParent( controlModel, XChild.class ) ); + form.setPropertyValue( "Command", m_foreignKeyTableName ); + form.setPropertyValue( "CommandType", CommandType.TABLE ); + form.setPropertyValue( "DataSourceName", m_database.getDocumentURL() ); + + // create list boxes for the different foreign keys + final String[][] fieldDefinitions = impl_getFieldTypeDefinitions(); + final String[] fieldTypes = fieldDefinitions[0]; + for ( int i=0; i<fieldTypes.length; ++i ) + { + final String tableName = fieldTypes[i] + "_pk"; + final String columnFKName = fieldTypes[i] + "_fk"; + final String columnPKName = fieldTypes[i] + "_pk"; + XPropertySet listBoxModel = m_formLayer.insertControlLine( "DatabaseListBox", columnFKName, null, 10, 20 + 10*i, 6 ); + listBoxModel.setPropertyValue( "Dropdown", Boolean.TRUE ); + listBoxModel.setPropertyValue( "ListSourceType", ListSourceType.SQL ); + listBoxModel.setPropertyValue( "ListSource", new String[] { "SELECT \"content\", \"" + columnPKName + + "\" FROM \"" + tableName + "\"" } ); + listBoxModel.setPropertyValue( "BoundColumn", Short.valueOf( (short)1 ) ); + } + + m_document.getCurrentView().toggleFormDesignMode(); + } + } diff --git a/forms/qa/integration/forms/ListSelection.java b/forms/qa/integration/forms/ListSelection.java new file mode 100644 index 000000000..5e9fd8a55 --- /dev/null +++ b/forms/qa/integration/forms/ListSelection.java @@ -0,0 +1,280 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.UnoRuntime; +import com.sun.star.beans.XPropertySet; +import com.sun.star.sheet.XSpreadsheet; +import com.sun.star.sheet.XSpreadsheets; +import com.sun.star.sheet.XSpreadsheetView; +import com.sun.star.container.XNamed; +import com.sun.star.container.XNameContainer; +import com.sun.star.container.XIndexContainer; +import com.sun.star.drawing.XDrawPageSupplier; +import com.sun.star.awt.XControlModel; +import com.sun.star.awt.XListBox; +import com.sun.star.script.XLibraryContainer; +import com.sun.star.script.XEventAttacherManager; +import com.sun.star.script.ScriptEventDescriptor; +import com.sun.star.accessibility.XAccessible; +import com.sun.star.accessibility.XAccessibleContext; +import com.sun.star.accessibility.XAccessibleSelection; +import com.sun.star.frame.XStorable; + +public class ListSelection extends integration.forms.TestCase +{ + /** Creates a new instance of ListSelection */ + public ListSelection() + { + super( DocumentType.CALC ); + } + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkUserListSelection" + }; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Form Control List Selection Test"; + } + + /* ------------------------------------------------------------------ */ + public void checkUserListSelection() throws com.sun.star.uno.Exception, java.lang.Exception + { + int runs = 5; + for ( int i = 0; i < runs; ++i ) + { + log.println( "Round " + ( i + 1 ) + " of " + runs ); + prepareDocument(); + impl_clickListBox(); + synchronized( this ) { this.wait( 1000 ); } + closeDocument(); + } + } + + /* ------------------------------------------------------------------ */ + final private void impl_clickListBox() + { + try + { + final int runs = 10; + java.util.Random generator = new java.util.Random(); + for ( int i = 0; i < runs; ++i ) + { + // obtain the active sheet + XSpreadsheetView view = m_document.getCurrentView().query( XSpreadsheetView.class ); + XSpreadsheet activeSheet = view.getActiveSheet(); + + // Accessibility access to the list box control in this sheet + XAccessible accessibleListBox = UnoRuntime.queryInterface( + XAccessible.class, getListBoxControl( activeSheet ) ); + XAccessibleContext context = accessibleListBox.getAccessibleContext(); + + // the first "accessible child" of a list box is its list + XAccessibleSelection accessibleList = UnoRuntime.queryInterface( + XAccessibleSelection.class, context.getAccessibleChild( 1 ) ); + + int selectPosition = generator.nextInt( 5 ); + String selectSheetName = getListBoxControl( activeSheet ).getItem( (short)selectPosition ); + accessibleList.selectAccessibleChild( selectPosition ); + try + { + synchronized( this ) + { + this.wait( 500 ); + } + } + catch( InterruptedException e ) { } + + XNamed sheetName = UnoRuntime.queryInterface( XNamed.class, view.getActiveSheet() ); + assure( "sheet was not selected as expected!", sheetName.getName().equals( selectSheetName ) ); + } + } + catch( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + failed( "caught an exception: " + e.toString() ); + } + } + + /* ------------------------------------------------------------------ */ + final private void impl_setupListenerScript() + { + try + { + XPropertySet docProps = dbfTools.queryPropertySet( m_document.getDocument() ); + XLibraryContainer basicLibs = UnoRuntime.queryInterface( + XLibraryContainer.class, docProps.getPropertyValue( "BasicLibraries" ) ); + XNameContainer basicLib = basicLibs.createLibrary( "default" ); + + String sListSelectionScript = + "Option Explicit\n" + + "\n" + + "Sub onListBoxSelected( oEvent as Object )\n" + + " Dim oView as Object\n" + + " Dim oSheet as Object\n" + + " Dim oSheets as Object\n" + + "\n" + + " Dim oControlModel as Object\n" + + " Dim sSheet as String\n" + + "\n" + + " if ( oEvent.Selected <> 65535 ) Then\n" + + " oControlModel = oEvent.Source.Model\n" + + " sSheet = oControlModel.StringItemList( oEvent.Selected )\n" + + "\n" + + " oSheets = thisComponent.Sheets\n" + + " oSheet = oSheets.getByName(sSheet)\n" + + "\n" + + " oView = thisComponent.CurrentController\n" + + " oView.setActiveSheet( oSheet )\n" + + " End If\n" + + "End Sub\n" + + "\n" + + "Sub onButtonClicked\n" + + " MsgBox \"clicked\"\n" + + "End Sub\n"; + + basicLib.insertByName( "callbacks", sListSelectionScript ); + } + catch( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + failed( "caught an exception: " + e.toString() ); + } + } + + /* ------------------------------------------------------------------ */ + final private void impl_assignStarBasicScript( XPropertySet controlModel, String interfaceName, String interfaceMethod, String scriptCode ) + { + try + { + XIndexContainer parentForm = (XIndexContainer)dbfTools.getParent( controlModel, XIndexContainer.class ); + + XEventAttacherManager manager = UnoRuntime.queryInterface( + XEventAttacherManager.class, parentForm ); + + int containerPosition = -1; + for ( int i = 0; i < parentForm.getCount(); ++i ) + { + XPropertySet child = dbfTools.queryPropertySet( parentForm.getByIndex( i ) ); + if ( child.equals( controlModel ) ) + { + containerPosition = i; + break; + } + } + manager.registerScriptEvent( containerPosition, new ScriptEventDescriptor( + interfaceName, + interfaceMethod, + "", + "StarBasic", + scriptCode + ) ); + } + catch( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + failed( "caught an exception: " + e.toString() ); + } + } + + /* ------------------------------------------------------------------ */ + @Override + protected void prepareDocument() throws com.sun.star.uno.Exception, java.lang.Exception + { + super.prepareDocument(); + impl_setupListenerScript(); + + SpreadsheetDocument document = (SpreadsheetDocument)m_document; + XSpreadsheets sheets = document.getSheets(); + + // delete all sheets except one + String[] sheetNames = sheets.getElementNames(); + for ( short i = 1; i < sheetNames.length; ++i ) + sheets.removeByName( sheetNames[ i ] ); + + // need 5 sheets + String[] newSheetNames = new String[] { "first", "second", "third", "forth", "fifth" }; + + // give the first one the right name + XNamed sheet = UnoRuntime.queryInterface( XNamed.class, + sheets.getByName( sheetNames[ 0 ] ) + ); + sheet.setName( newSheetNames[ 0 ] ); + + // add some dummy buttons + for ( int i = 0; i < 4; ++i ) + { + XPropertySet buttonModel = m_formLayer.createControlAndShape( "CommandButton", 10, 10 + i * 10, 30, 8 ); + impl_assignStarBasicScript( buttonModel, "XActionListener", "actionPerformed", "document:default.callbacks.onButtonClicked" ); + } + + // and a list box + XPropertySet listBox = m_formLayer.createControlAndShape( "ListBox", 50, 10, 40, 6 ); + listBox.setPropertyValue( "Dropdown", Boolean.TRUE ); + listBox.setPropertyValue( "StringItemList", newSheetNames ); + listBox.setPropertyValue( "Name", "ListBox" ); + + impl_assignStarBasicScript( listBox, "XItemListener", "itemStateChanged", "document:default.callbacks.onListBoxSelected" ); + + // clone this sheet + for ( short i = 1; i < newSheetNames.length; ++i ) + sheets.copyByName( newSheetNames[0], newSheetNames[i], i ); + + // switch the thing to alive mode + m_document.getCurrentView().toggleFormDesignMode(); + + try + { + XStorable storable = m_document.query( XStorable.class ); + java.io.File testFile = java.io.File.createTempFile( getTestObjectName(),".ods"); + storable.storeAsURL( testFile.getAbsoluteFile().toURI().toURL().toString(), new com.sun.star.beans.PropertyValue[]{} ); + testFile.deleteOnExit(); + } + catch( java.lang.Throwable e ) + { + e.printStackTrace(); + failed( "caught an exception: " + e.toString() ); + } + } + + /* ------------------------------------------------------------------ */ + protected XControlModel getListBoxModel( XSpreadsheet sheet ) + { + XDrawPageSupplier suppPage = UnoRuntime.queryInterface( + XDrawPageSupplier.class, sheet ); + FormComponent formsRoot = new FormComponent( suppPage.getDrawPage() ); + XControlModel listBoxModel = formsRoot.getByIndex( 0 ). + getByName( "ListBox" ).query( XControlModel.class ); + return listBoxModel; + } + + /* ------------------------------------------------------------------ */ + protected XListBox getListBoxControl( XSpreadsheet sheet ) throws com.sun.star.uno.Exception + { + return UnoRuntime.queryInterface( + XListBox.class, m_document.getCurrentView().getControl( getListBoxModel( sheet ) ) ); + } + } diff --git a/forms/qa/integration/forms/ListSelection.props b/forms/qa/integration/forms/ListSelection.props new file mode 100644 index 000000000..92b56bef9 --- /dev/null +++ b/forms/qa/integration/forms/ListSelection.props @@ -0,0 +1 @@ +ThreadTimeOut=600000 diff --git a/forms/qa/integration/forms/ListSelectionValidator.java b/forms/qa/integration/forms/ListSelectionValidator.java new file mode 100644 index 000000000..164110d30 --- /dev/null +++ b/forms/qa/integration/forms/ListSelectionValidator.java @@ -0,0 +1,51 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +public class ListSelectionValidator extends integration.forms.ControlValidator +{ + public String explainInvalid( Object Value ) + { + try + { + short[] selectionIndexes = (short[])Value; + if ( selectionIndexes.length > 2 ) + return "please 2 entries, at most"; + } + catch( java.lang.Exception e ) + { + return "oops. What's this?"; + } + return ""; + } + + public boolean isValid( Object Value ) + { + try + { + short[] selectionIndexes = (short[])Value; + return selectionIndexes.length <= 2; + } + catch( java.lang.Exception e ) + { + } + return false; + } + +} diff --git a/forms/qa/integration/forms/MasterDetailForms.java b/forms/qa/integration/forms/MasterDetailForms.java new file mode 100644 index 000000000..4b448fe04 --- /dev/null +++ b/forms/qa/integration/forms/MasterDetailForms.java @@ -0,0 +1,400 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.beans.NamedValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.XIndexContainer; + +import com.sun.star.uno.UnoRuntime; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.container.XNameContainer; +import com.sun.star.embed.XComponentSupplier; +import com.sun.star.form.XGridColumnFactory; +import com.sun.star.form.XGridFieldDataSupplier; +import com.sun.star.form.XLoadable; +import com.sun.star.lang.XComponent; +import com.sun.star.sdb.CommandType; +import com.sun.star.sdb.XFormDocumentsSupplier; +import com.sun.star.sdbc.SQLException; +import com.sun.star.sdbc.XColumnLocate; +import com.sun.star.ucb.Command; +import com.sun.star.ucb.OpenMode; +import com.sun.star.ucb.XCommandProcessor; +import com.sun.star.uno.Type; +import com.sun.star.util.XModifiable; +import connectivity.tools.CRMDatabase; +import connectivity.tools.HsqlColumnDescriptor; +import connectivity.tools.HsqlDatabase; +import connectivity.tools.HsqlTableDescriptor; +import org.openoffice.complex.forms.tools.ResultSet; + + +public class MasterDetailForms extends complexlib.ComplexTestCase implements com.sun.star.form.XLoadListener +{ + private XMultiServiceFactory m_orb; + + private XPropertySet m_masterForm; + private XPropertySet m_detailForm; + private ResultSet m_masterResult; + private ResultSet m_detailResult; + + final private Object m_waitForLoad = new Object(); + private boolean m_loaded = false; + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkMultipleKeys", + "checkDetailFormDefaults" + }; + } + + /* ------------------------------------------------------------------ */ + public void before() + { + m_orb = param.getMSF(); + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Form Control Spreadsheet Cell Binding Test"; + } + + /* ------------------------------------------------------------------ */ + /** creates the table structure needed for the test + */ + private void impl_createTableStructure( final HsqlDatabase _databaseDocument ) throws SQLException + { + HsqlColumnDescriptor[] masterColumns = { + new HsqlColumnDescriptor( "ID1", "INTEGER", HsqlColumnDescriptor.PRIMARY ), + new HsqlColumnDescriptor( "ID2", "INTEGER", HsqlColumnDescriptor.PRIMARY ), + new HsqlColumnDescriptor( "value", "VARCHAR(50)" ), + }; + HsqlColumnDescriptor[] detailColumns = { + new HsqlColumnDescriptor( "ID", "INTEGER", HsqlColumnDescriptor.PRIMARY ), + new HsqlColumnDescriptor( "FK_ID1", "INTEGER", HsqlColumnDescriptor.REQUIRED, "master", "ID1" ), + new HsqlColumnDescriptor( "FK_ID2", "INTEGER", HsqlColumnDescriptor.REQUIRED, "master", "ID2" ), + new HsqlColumnDescriptor( "name", "VARCHAR(50)" ), + }; + _databaseDocument.createTable( new HsqlTableDescriptor( "master", masterColumns ) ); + _databaseDocument.createTable( new HsqlTableDescriptor( "detail", detailColumns ) ); + + _databaseDocument.executeSQL( "INSERT INTO \"master\" VALUES ( 1, 1, 'First Record' )" ); + _databaseDocument.executeSQL( "INSERT INTO \"master\" VALUES ( 1, 2, 'Second Record' )" ); + _databaseDocument.executeSQL( "INSERT INTO \"detail\" VALUES ( 1, 1, 1, 'record 1.1 (1)')"); + _databaseDocument.executeSQL( "INSERT INTO \"detail\" VALUES ( 2, 1, 1, 'record 1.1 (2)')"); + _databaseDocument.executeSQL( "INSERT INTO \"detail\" VALUES ( 3, 1, 2, 'record 1.2 (1)')"); + + _databaseDocument.defaultConnection().refreshTables(); + } + + /* ------------------------------------------------------------------ */ + private void impl_createForms( final HsqlDatabase _databaseDocument ) throws com.sun.star.uno.Exception + { + m_masterForm = dbfTools.queryPropertySet( m_orb.createInstance( "com.sun.star.form.component.DataForm" ) ); + m_masterForm.setPropertyValue( "ActiveConnection", _databaseDocument.defaultConnection().getXConnection() ); + m_masterForm.setPropertyValue( "CommandType", Integer.valueOf( com.sun.star.sdb.CommandType.TABLE ) ); + m_masterForm.setPropertyValue( "Command", "master" ); + + m_masterResult = new ResultSet( m_masterForm ); + + m_detailForm = dbfTools.queryPropertySet( m_orb.createInstance( "com.sun.star.form.component.DataForm" ) ); + m_detailForm.setPropertyValue( "ActiveConnection", _databaseDocument.defaultConnection().getXConnection() ); + m_detailForm.setPropertyValue( "CommandType", Integer.valueOf( com.sun.star.sdb.CommandType.TABLE ) ); + m_detailForm.setPropertyValue( "Command", "detail" ); + + m_detailResult = new ResultSet( m_detailForm ); + + XNameContainer masterContainer = UnoRuntime.queryInterface( XNameContainer.class, m_masterForm ); + masterContainer.insertByName( "slave", m_detailForm ); + } + + /* ------------------------------------------------------------------ */ + /** checks if master-detail relationships including multiple keys work + */ + public void checkMultipleKeys() throws com.sun.star.uno.Exception, java.lang.Exception + { + HsqlDatabase databaseDocument = null; + try + { + databaseDocument = new HsqlDatabase( m_orb ); + impl_createTableStructure( databaseDocument ); + impl_createForms( databaseDocument ); + + m_detailForm.setPropertyValue( "MasterFields", new String[] { "ID1", "ID2" } ); + m_detailForm.setPropertyValue( "DetailFields", new String[] { "FK_ID1", "FK_ID2" } ); + + XLoadable loadMaster = UnoRuntime.queryInterface( XLoadable.class, m_masterForm ); + XLoadable loadDetail = UnoRuntime.queryInterface( XLoadable.class, m_detailForm ); + loadDetail.addLoadListener( this ); + + // wait until the detail form is loaded + loadMaster.load(); + impl_waitForLoadedEvent(); + + // okay, now the master form should be on the first record + assure( "wrong form state after loading (ID1)", m_masterResult.getInt(1) == 1 ); + assure( "wrong form state after loading (ID2)", m_masterResult.getInt(2) == 1 ); + assure( "wrong form state after loading (value)", m_masterResult.getString(3).equals( "First Record" ) ); + + // the values in the linked fields should be identical + int expectedDetailRowCounts[] = { 2, 1 }; + do + { + verifyColumnValueIdentity( "ID1", "FK_ID1" ); + verifyColumnValueIdentity( "ID2", "FK_ID2" ); + + m_detailResult.last(); + int masterPos = m_masterResult.getRow(); + assure( "wrong number of records in detail form, for master form at pos " + masterPos, + ((Integer)m_detailForm.getPropertyValue( "RowCount" )).intValue() == expectedDetailRowCounts[ masterPos - 1 ] ); + + if (!m_masterResult.next()) + return; + impl_waitForLoadedEvent(); + } + while ( !m_masterResult.isAfterLast() ); + assure( "wrong number of records in master form", 2 == ((Integer)m_masterForm.getPropertyValue( "RowCount" )).intValue() ); + } + finally + { + if ( databaseDocument != null ) + databaseDocument.closeAndDelete(); + impl_cleanUpStep(); + } + } + + /* ------------------------------------------------------------------ */ + private final void impl_cleanUpStep() + { + if ( m_masterForm != null ) + dbfTools.disposeComponent( m_masterForm ); + if ( m_detailForm != null ) + dbfTools.disposeComponent( m_detailForm ); + m_masterForm = m_detailForm = null; + } + + /* ------------------------------------------------------------------ */ + /** checks whether default values in detail forms work as expected. + * + * Effectively, this test case verifies the issues #i106574# and #i105235# did not creep back in. + */ + public void checkDetailFormDefaults() throws Exception + { + CRMDatabase database = null; + XCommandProcessor subComponentCommands = null; + try + { + // create our standard CRM database document + database = new CRMDatabase( m_orb, true ); + + // create a form document therein + XFormDocumentsSupplier formDocSupp = UnoRuntime.queryInterface( XFormDocumentsSupplier.class, database.getDatabase().getModel() ); + XMultiServiceFactory formFactory = UnoRuntime.queryInterface( XMultiServiceFactory.class, formDocSupp.getFormDocuments() ); + NamedValue[] loadArgs = new NamedValue[] { + new NamedValue( "ActiveConnection", database.getConnection().getXConnection() ), + new NamedValue( "MediaType", "application/vnd.oasis.opendocument.text" ) + }; + + subComponentCommands = UnoRuntime.queryInterface( + XCommandProcessor.class, + formFactory.createInstanceWithArguments( "com.sun.star.sdb.DocumentDefinition", loadArgs ) ); + Command command = new Command(); + command.Name = "openDesign"; + command.Argument = Short.valueOf( OpenMode.DOCUMENT ); + + DocumentHelper subDocument = new DocumentHelper( m_orb, + UnoRuntime.queryInterface( XComponent.class, + subComponentCommands.execute( command, subComponentCommands.createCommandIdentifier(), null ) + ) + ); + FormLayer formLayer = new FormLayer( subDocument ); + XPropertySet controlModel = formLayer.insertControlLine( "DatabaseNumericField", "ID", "", 10 ); + formLayer.insertControlLine( "DatabaseTextField", "Name", "", 20 ); + formLayer.insertControlLine( "DatabaseTextField", "Description", "", 30 ); + + m_masterForm = (XPropertySet)dbfTools.getParent( controlModel, XPropertySet.class ); + m_masterForm.setPropertyValue( "Command", "categories" ); + m_masterForm.setPropertyValue( "CommandType", Integer.valueOf( CommandType.TABLE ) ); + + // create a detail form + m_detailForm = UnoRuntime.queryInterface( XPropertySet.class, subDocument.createSubForm( m_masterForm, "products" ) ); + m_detailForm.setPropertyValue( "Command", "SELECT \"ID\", \"Name\", \"CategoryID\" FROM \"products\"" ); + m_detailForm.setPropertyValue( "CommandType", Integer.valueOf( CommandType.COMMAND ) ); + m_detailForm.setPropertyValue( "MasterFields", new String[] { "ID" } ); + m_detailForm.setPropertyValue( "DetailFields", new String[] { "CategoryID" } ); + + // create a grid control in the detail form, with some columns + XPropertySet gridControlModel = formLayer.createControlAndShape( "GridControl", 20, 40, 130, 50, m_detailForm ); + gridControlModel.setPropertyValue( "Name", "product list" ); + XIndexContainer gridColumns = UnoRuntime.queryInterface( XIndexContainer.class, gridControlModel ); + impl_createGridColumn( gridColumns, "TextField", "ID" ); + XPropertySet nameColumn = impl_createGridColumn( gridColumns, "TextField", "Name" ); + nameColumn.setPropertyValue( "Width", Integer.valueOf( 600 ) ); // 6 cm + nameColumn.setPropertyValue( "DefaultText", "default text" ); + + // go live + m_masterResult = new ResultSet( m_masterForm ); + m_detailResult = new ResultSet( m_detailForm ); + + XLoadable loadDetail = UnoRuntime.queryInterface( XLoadable.class, m_detailForm ); + loadDetail.addLoadListener( this ); + + subDocument.getCurrentView().toggleFormDesignMode(); + impl_waitForLoadedEvent(); + + // now that we set up this, do the actual tests + // First, https://bz.apache.org/ooo/show_bug.cgi?id=105235 described the problem + // that default values in the sub form didn't work when the master form was navigated to a row + // for which no detail records were present, and the default of the column/control is the same + // as the last known value. + + // so, take the current value of the "Name" column, and set it as default value ... + String defaultValue = m_detailResult.getString( 2 ); + nameColumn.setPropertyValue( "DefaultText", defaultValue ); + // ... then move to the second main form row ... + m_masterResult.absolute( 2 ); + impl_waitForLoadedEvent(); + // ... which should result in an empty sub form ... + assure( "test precondition not met: The second master form record is expected to have no detail records, " + + "else the test becomes meaningless", impl_isNewRecord( m_detailForm ) ); + // ... and in the "Name" column having the proper text + String actualValue = (String)nameColumn.getPropertyValue( "Text" ); + assureEquals( "#i105235#: default value in sub form not working (not propagated to column model)", defaultValue, actualValue ); + // However, checking the column model's value alone is not enough - we need to ensure it is properly + // propagated to the control. + XGridFieldDataSupplier gridData = subDocument.getCurrentView().getControl( + gridControlModel, XGridFieldDataSupplier.class ); + actualValue = (String)(gridData.queryFieldData( 0, Type.STRING )[1]); + assureEquals( "#i105235#: default value in sub form not working (not propagated to column)", defaultValue, actualValue ); + } + finally + { + if ( subComponentCommands != null ) + { + XComponentSupplier componentSupplier = UnoRuntime.queryInterface( XComponentSupplier.class, subComponentCommands ); + XModifiable modifySubComponent = UnoRuntime.queryInterface( XModifiable.class, componentSupplier.getComponent() ); + modifySubComponent.setModified( false ); + Command command = new Command(); + command.Name = "close"; + subComponentCommands.execute( command, subComponentCommands.createCommandIdentifier(), null ); + } + + if ( database != null ) + database.saveAndClose(); + + impl_cleanUpStep(); + } + } + + /* ------------------------------------------------------------------ */ + private boolean impl_isNewRecord( final XPropertySet _rowSet ) + { + boolean isNew = false; + try + { + isNew = ((Boolean)_rowSet.getPropertyValue( "IsNew" )).booleanValue(); + } + catch ( Exception ex ) + { + failed( "obtaining the IsNew property failed" ); + } + return isNew; + } + + /* ------------------------------------------------------------------ */ + private XPropertySet impl_createGridColumn( final XIndexContainer _gridModel, final String _columnType, final String _boundField ) throws Exception + { + final XGridColumnFactory columnFactory = UnoRuntime.queryInterface( XGridColumnFactory.class, _gridModel ); + XPropertySet column = columnFactory.createColumn( _columnType ); + column.setPropertyValue( "DataField", _boundField ); + column.setPropertyValue( "Name", _boundField ); + column.setPropertyValue( "Label", _boundField ); + _gridModel.insertByIndex( _gridModel.getCount(), column ); + return column; + } + + private void impl_waitForLoadedEvent() + { + synchronized( m_waitForLoad ) + { + while ( !m_loaded ) + { + try { m_waitForLoad.wait(); } + catch( InterruptedException e ) { } + } + // reset the flag for the next time + m_loaded = false; + } + } + + /** assures that the (integer) values in the given columns of our master and detail forms are identical + */ + private void verifyColumnValueIdentity( final String masterColName, final String detailColName ) throws SQLException + { + XColumnLocate locateMasterCols = UnoRuntime.queryInterface( XColumnLocate.class, m_masterForm ); + XColumnLocate locateDetailCols = UnoRuntime.queryInterface( XColumnLocate.class, m_detailForm ); + + int masterValue = m_masterResult.getInt( locateMasterCols.findColumn( masterColName ) ); + int detailValue = m_detailResult.getInt( locateDetailCols.findColumn( detailColName ) ); + + assure( "values in linked column pair " + detailColName + "->" + masterColName + " (" + + detailValue + "->" + masterValue + ") do not match (master position: " + m_masterResult.getRow() + ")!", + masterValue == detailValue ); + } + + public void disposing(com.sun.star.lang.EventObject eventObject) + { + } + + public void loaded(com.sun.star.lang.EventObject eventObject) + { + synchronized( m_waitForLoad ) + { + m_loaded = true; + m_waitForLoad.notify(); + } + } + + public void reloaded(com.sun.star.lang.EventObject eventObject) + { + synchronized( m_waitForLoad ) + { + m_loaded = true; + m_waitForLoad.notify(); + } + } + + public void reloading(com.sun.star.lang.EventObject eventObject) + { + } + + public void unloaded(com.sun.star.lang.EventObject eventObject) + { + } + + public void unloading(com.sun.star.lang.EventObject eventObject) + { + } +} diff --git a/forms/qa/integration/forms/NumericValidator.java b/forms/qa/integration/forms/NumericValidator.java new file mode 100644 index 000000000..06370cfb3 --- /dev/null +++ b/forms/qa/integration/forms/NumericValidator.java @@ -0,0 +1,68 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +public class NumericValidator extends integration.forms.ControlValidator +{ + + public String explainInvalid( Object Value ) + { + try + { + double value = ((Double)Value).doubleValue(); + if ( Double.compare( Double.NaN, value ) == 0 ) + return "This is NotANumber"; + if ( !isProperRange( value ) ) + return "The value must be between 0 and 100"; + if ( !isProperDigitCount( value ) ) + return "The value must have at most one decimal digit"; + } + catch( java.lang.Exception e ) + { + return "This is no valid number"; + } + return ""; + } + + public boolean isValid( Object Value ) + { + try + { + double value = ((Double)Value).doubleValue(); + if ( Double.compare( Double.NaN, value ) == 0 ) + return false; + if ( !isProperRange( value ) ) + return false; + return isProperDigitCount( value ); + } + catch( java.lang.Exception e ) + { + } + return false; + } + + private boolean isProperRange( double value) + { + return ( value >= 0 ) && ( value <= 100 ); + } + + private boolean isProperDigitCount( double value) + { + return ( Math.floor( value * 10 ) == value * 10 ); + } +} diff --git a/forms/qa/integration/forms/RadioButtons.java b/forms/qa/integration/forms/RadioButtons.java new file mode 100644 index 000000000..4b6e5de6f --- /dev/null +++ b/forms/qa/integration/forms/RadioButtons.java @@ -0,0 +1,426 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.util.*; +import com.sun.star.lang.*; +import com.sun.star.container.*; +import com.sun.star.beans.*; +import com.sun.star.awt.XRadioButton; +import java.util.Arrays; + +public class RadioButtons extends complexlib.ComplexTestCase +{ + private DocumentHelper m_document; /// our current test document + private FormLayer m_formLayer; /// quick access to the form layer + private XMultiServiceFactory m_orb; /// our service factory + private XPropertySet m_primaryForm; /// the primary form, to be used in text documents and in the first page of spreadsheets + private XPropertySet m_secondaryForm; /// the secondary form, to be used in the second page of spreadsheets + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkSingleButtons", + "checkThreeGroups", + "checkMultipleForms", + "checkCalcPageSwitch" + }; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Form Radio Buttons Test"; + } + + /* ------------------------------------------------------------------ */ + public void before() throws java.lang.Exception + { + m_orb = param.getMSF(); + } + + /* ------------------------------------------------------------------ */ + private XPropertySet insertRadio( int nXPos, int nYPos, String label, String name, String refValue ) throws com.sun.star.uno.Exception, java.lang.Exception + { + return insertRadio( nXPos, nYPos, label, name, refValue, null ); + } + + /* ------------------------------------------------------------------ */ + private XPropertySet insertRadio( int nXPos, int nYPos, String label, String name, String refValue, XPropertySet parentForm ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XIndexContainer parentContainer = dbfTools.queryIndexContainer( parentForm ); + XPropertySet xRadio = m_formLayer.createControlAndShape( "DatabaseRadioButton", nXPos, nYPos, 25, 6, parentContainer ); + xRadio.setPropertyValue( "Label", label ); + xRadio.setPropertyValue( "RefValue", refValue ); + xRadio.setPropertyValue( "Name", name ); + + if ( null == m_primaryForm ) + m_primaryForm = (XPropertySet)dbfTools.getParent( xRadio, XPropertySet.class ); + + return xRadio; + } + + /* ------------------------------------------------------------------ */ + /** this checks whether n groups of radio buttons, consisting of only one button each, + * behave properly + */ + public void checkSingleButtons() throws com.sun.star.uno.Exception, java.lang.Exception + { + prepareTestStep( false ); + + insertRadio( 20, 30, "group 1", "group 1", "" ); + insertRadio( 20, 38, "group 2", "group 2", "" ); + insertRadio( 20, 46, "group 3", "group 3", "" ); + insertRadio( 20, 54, "group 4", "group 4", "" ); + + // switch to alive mode + m_document.getCurrentView( ).toggleFormDesignMode( ); + + checkRadio( "group 1", "" ); + verifySingleRadios( 1, 0, 0, 0 ); + + checkRadio( "group 4", "" ); + verifySingleRadios( 1, 0, 0, 1 ); + + checkRadio( "group 2", "" ); + verifySingleRadios( 1, 1, 0, 1 ); + + checkRadio( "group 3", "" ); + verifySingleRadios( 1, 1, 1, 1 ); + + cleanupTestStep(); + } + + /* ------------------------------------------------------------------ */ + /** creates three groups of radio buttons in a sample document, and checks whether they're working + */ + public void checkThreeGroups( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + prepareTestStep( false ); + + insertRadio( 20, 30, "group 1 (a)", "group 1", "a" ); + insertRadio( 20, 38, "group 1 (b)", "group 1", "b" ); + + insertRadio( 20, 50, "group 2 (a)", "group 2", "a" ); + insertRadio( 20, 58, "group 2 (b)", "group 2", "b" ); + + insertRadio( 20, 70, "group 3 (a)", "group 3", "a" ); + insertRadio( 20, 78, "group 3 (b)", "group 3", "b" ); + + // switch to alive mode + m_document.getCurrentView( ).toggleFormDesignMode( ); + + // initially, after switching to alive mode, all buttons should be unchecked + verifySixPack( 0, 0, 0, 0, 0, 0 ); + + // check one button in every group + checkRadio( "group 1", "a" ); + checkRadio( "group 2", "b" ); + checkRadio( "group 3", "a" ); + // and verify that this worked + verifySixPack( 1, 0, 0, 1, 1, 0 ); + + // check all buttons which are currently unchecked + checkRadio( "group 1", "b" ); + checkRadio( "group 2", "a" ); + checkRadio( "group 3", "b" ); + // this should have reset the previous checks in the respective groups + verifySixPack( 0, 1, 1, 0, 0, 1 ); + + // and back to the previous check state + checkRadio( "group 1", "a" ); + checkRadio( "group 2", "b" ); + checkRadio( "group 3", "a" ); + verifySixPack( 1, 0, 0, 1, 1, 0 ); + + cleanupTestStep(); + } + + /* ------------------------------------------------------------------ */ + /** tests whether radio buttons which belong to different forms behave properly + */ + public void checkMultipleForms( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + prepareTestStep( false ); + + insertRadio( 20, 30, "group 1 (a)", "group 1", "a" ); + insertRadio( 20, 38, "group 1 (b)", "group 1", "b" ); + insertRadio( 20, 46, "group 1 (c)", "group 1", "c" ); + + m_secondaryForm = dbfTools.queryPropertySet( m_document.createSiblingForm( m_primaryForm, "secondary" ) ); + + insertRadio( 70, 30, "group 2 (a)", "group 2", "a", m_secondaryForm ); + insertRadio( 70, 38, "group 2 (b)", "group 2", "b", m_secondaryForm ); + insertRadio( 70, 46, "group 2 (c)", "group 2", "c", m_secondaryForm ); + + // switch to alive mode + m_document.getCurrentView( ).toggleFormDesignMode( ); + + // play around with different check states + checkRadio( "group 1", "b", m_primaryForm ); + checkRadio( "group 2", "c", m_secondaryForm ); + verifyTwoFormRadios( 0, 1, 0, 0, 0, 1 ); + + checkRadio( "group 1", "c", m_primaryForm ); + verifyTwoFormRadios( 0, 0, 1, 0, 0, 1 ); + + checkRadio( "group 2", "a", m_secondaryForm ); + verifyTwoFormRadios( 0, 0, 1, 1, 0, 0 ); + + checkRadio( "group 1", "a", m_primaryForm ); + verifyTwoFormRadios( 1, 0, 0, 1, 0, 0 ); + + checkRadio( "group 2", "b", m_secondaryForm ); + verifyTwoFormRadios( 1, 0, 0, 0, 1, 0 ); + + cleanupTestStep(); + } + + /* ------------------------------------------------------------------ */ + /** tests for a special bug which we once had, where radio buttons lost their state after + * switching spreadsheet pages + */ + public void checkCalcPageSwitch( ) throws com.sun.star.uno.Exception, java.lang.Exception + { + prepareTestStep( true ); + + m_formLayer.setInsertPage( 0 ); + insertRadio( 15, 20, "group 1 (a)", "group 1", "a" ); + insertRadio( 15, 26, "group 1 (b)", "group 1", "b" ); + + m_formLayer.setInsertPage( 1 ); + XPropertySet xRadio = insertRadio( 15, 20, "group 2 (a)", "group 2", "a" ); + insertRadio( 15, 26, "group 2 (b)", "group 2", "b" ); + m_secondaryForm = (XPropertySet)dbfTools.getParent( xRadio, XPropertySet.class ); + + // switch to alive mode + SpreadsheetView view = (SpreadsheetView)m_document.getCurrentView( ); + view.toggleFormDesignMode( ); + // and do initial checking + checkRadio( "group 1", "a", m_primaryForm ); + view.activateSheet( 1 ); + checkRadio( "group 2", "b", m_secondaryForm ); + + // see whether the check states on the first page survived the page switch + verifySheetRadios( 1, 0, 0, 1 ); + // switch back to the first sheet, and see whether the check states survived + view.activateSheet( 0 ); + verifySheetRadios( 1, 0, 0, 1 ); + // and for completely, check again after switching to third sheet and back to the first + view.activateSheet( 2 ); + view.activateSheet( 1 ); + verifySheetRadios( 1, 0, 0, 1 ); + + cleanupTestStep(); + } + + /* ------------------------------------------------------------------ */ + public void after() + { + closeDocument(); + } + + /* ------------------------------------------------------------------ */ + /** closes our document, if we have an open one + */ + private void closeDocument() + { + try + { + // close our document + if ( m_document != null ) + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, + m_document.getDocument() ); + closeDoc.close( true ); + } + } + catch ( com.sun.star.uno.Exception e ) + { + } + } + + /* ------------------------------------------------------------------ */ + private void prepareTestStep( boolean useSpreadsheetDocument ) throws com.sun.star.uno.Exception, java.lang.Exception + { + m_primaryForm = null; + + m_document = useSpreadsheetDocument ? new SpreadsheetDocument( m_orb ) : DocumentHelper.blankTextDocument( m_orb ); + m_formLayer = new FormLayer( m_document ); + } + + /* ------------------------------------------------------------------ */ + private void cleanupTestStep( ) + { + closeDocument(); + } + + /* ------------------------------------------------------------------ */ + /** checks or unchecks the radio button (in our primary form) with the given name and the given ref value + */ + private void checkRadio( String groupName, String refValue ) throws com.sun.star.uno.Exception, java.lang.Exception + { + checkRadio( groupName, refValue, m_primaryForm ); + } + + /* ------------------------------------------------------------------ */ + /** checks or unchecks the radio button with the given name and the given ref value + */ + private void checkRadio( String groupName, String refValue, XPropertySet form ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet xRadio = getRadioModel( groupName, refValue, form ); + + XRadioButton radioButton = UnoRuntime.queryInterface( + XRadioButton.class, m_document.getCurrentView().getControl( xRadio ) ); + radioButton.setState( true ); + } + + /* ------------------------------------------------------------------ */ + private XPropertySet getRadioModel( String name, String refValue ) throws com.sun.star.uno.Exception, java.lang.Exception + { + return getRadioModel( name, refValue, m_primaryForm ); + } + + /* ------------------------------------------------------------------ */ + private XPropertySet getRadioModel( String name, String refValue, XPropertySet form ) throws com.sun.star.uno.Exception, java.lang.Exception + { + return m_formLayer.getRadioModelByRefValue( form, name, refValue ); + } + + /* ------------------------------------------------------------------ */ + /** verifies a number of radio buttons for their states + */ + private boolean verifyRadios( XPropertySet[] radios, short[] expectedStates, String errorMessage ) throws com.sun.star.uno.Exception + { + short[] actualStates = new short[radios.length]; + + // collect all current states. This is just to be able to emit them, in case of a failure + for ( int i = 0; i<radios.length; ++i ) + { + actualStates[i] = ((Short)radios[i].getPropertyValue( "State" )).shortValue(); + } + + // now actually check the states + for ( int i = 0; i<radios.length; ++i ) + { + if ( actualStates[i] != expectedStates[i] ) + { + failed( errorMessage + " (expected: " + Arrays.toString( expectedStates ) + ", found: " + Arrays.toString( actualStates ) + ")" ); + return false; + } + } + + return true; + } + + /* ------------------------------------------------------------------ */ + /** verifies the states of the 4 radio buttons from the checkSingleButtons test + */ + private boolean verifySingleRadios( int state1, int state2, int state3, int state4 ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet[] radios = new XPropertySet[4]; + radios[0] = getRadioModel( "group 1", "" ); + radios[1] = getRadioModel( "group 2", "" ); + radios[2] = getRadioModel( "group 3", "" ); + radios[3] = getRadioModel( "group 4", "" ); + + short[] states = new short[4]; + states[0] = (short)state1; + states[1] = (short)state2; + states[2] = (short)state3; + states[3] = (short)state4; + + return verifyRadios( radios, states, "single-group radio buttons do not work!" ); + } + + /* ------------------------------------------------------------------ */ + /** verifies the states of 6 radio buttons form the checkThreeGroups test + */ + private boolean verifySixPack( XPropertySet[] radios, String errorMessage, + int state1, int state2, int state3, int state4, int state5, int state6 ) throws com.sun.star.uno.Exception, java.lang.Exception + { + short[] states = new short[6]; + states[0] = (short)state1; + states[1] = (short)state2; + states[2] = (short)state3; + states[3] = (short)state4; + states[4] = (short)state5; + states[5] = (short)state6; + + return verifyRadios( radios, states, errorMessage ); + } + + /* ------------------------------------------------------------------ */ + /** verifies the states of 6 radio buttons + */ + private boolean verifySixPack( int state1, int state2, int state3, int state4, int state5, int state6 ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet[] radios = new XPropertySet[6]; + radios[0] = getRadioModel( "group 1", "a" ); + radios[1] = getRadioModel( "group 1", "b" ); + radios[2] = getRadioModel( "group 2", "a" ); + radios[3] = getRadioModel( "group 2", "b" ); + radios[4] = getRadioModel( "group 3", "a" ); + radios[5] = getRadioModel( "group 3", "b" ); + + return verifySixPack( radios, "six radio buttons, forming three different groups, do not properly work!", + state1, state2, state3, state4, state5, state6 ); + } + + /* ------------------------------------------------------------------ */ + /** verifies the states of the 6 radio buttons in our checkMultipleForms test + */ + private boolean verifyTwoFormRadios( int state1, int state2, int state3, int state4, int state5, int state6 ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet[] radios = new XPropertySet[6]; + radios[0] = getRadioModel( "group 1", "a", m_primaryForm ); + radios[1] = getRadioModel( "group 1", "b", m_primaryForm ); + radios[2] = getRadioModel( "group 1", "c", m_primaryForm ); + radios[3] = getRadioModel( "group 2", "a", m_secondaryForm ); + radios[4] = getRadioModel( "group 2", "b", m_secondaryForm ); + radios[5] = getRadioModel( "group 2", "c", m_secondaryForm ); + + return verifySixPack( radios, "radio buttons on different forms do not work properly!", + state1, state2, state3, state4, state5, state6 ); + } + + /* ------------------------------------------------------------------ */ + /** verifies the states of the 4 radio buttons in our spreadsheet document (checkCalcPageSwitch) + */ + private boolean verifySheetRadios( int state1, int state2, int state3, int state4 ) throws com.sun.star.uno.Exception, java.lang.Exception + { + XPropertySet[] radios = new XPropertySet[4]; + radios[0] = getRadioModel( "group 1", "a", m_primaryForm ); + radios[1] = getRadioModel( "group 1", "b", m_primaryForm ); + radios[2] = getRadioModel( "group 2", "a", m_secondaryForm ); + radios[3] = getRadioModel( "group 2", "b", m_secondaryForm ); + + short[] states = new short[4]; + states[0] = (short)state1; + states[1] = (short)state2; + states[2] = (short)state3; + states[3] = (short)state4; + + return verifyRadios( radios, states, "seems some of the radio button check states didn't survive the page activation(s)!" ); + } +} + diff --git a/forms/qa/integration/forms/SingleControlValidation.java b/forms/qa/integration/forms/SingleControlValidation.java new file mode 100644 index 000000000..0873d34ac --- /dev/null +++ b/forms/qa/integration/forms/SingleControlValidation.java @@ -0,0 +1,170 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.beans.*; +import com.sun.star.form.validation.*; + +public class SingleControlValidation implements XFormComponentValidityListener +{ + private final DocumentHelper m_document; /// our current test document + private final FormLayer m_formLayer; /// quick access to the form layer + + private XPropertySet m_inputField; + private XPropertySet m_inputLabel; + private XPropertySet m_statusField; + private XPropertySet m_explanationField; + private final XValidator m_validator; + + /* ------------------------------------------------------------------ */ + public SingleControlValidation( DocumentHelper document, int columnPos, int rowPos, String formComponentService, XValidator validator ) + { + m_document = document; + m_validator = validator; + m_formLayer = new FormLayer( m_document ); + createControls( columnPos, rowPos, formComponentService, 1, 0 ); + } + + /* ------------------------------------------------------------------ */ + public SingleControlValidation( DocumentHelper document, int columnPos, int rowPos, String formComponentService, XValidator validator, int controlCount, int controlHeight ) + { + m_document = document; + m_validator = validator; + m_formLayer = new FormLayer( m_document ); + createControls( columnPos, rowPos, formComponentService, controlCount, controlHeight ); + } + + /* ------------------------------------------------------------------ */ + public XPropertySet getInputField() + { + return m_inputField; + } + + /* ------------------------------------------------------------------ */ + public void setExplanatoryText( String text ) + { + try + { + m_inputLabel.setPropertyValue( "Label", text ); + } + catch( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + } + } + + /* ------------------------------------------------------------------ */ + private void createControls( int columnPos, int rowPos, String formComponentService, int controlCount, int controlHeight ) + { + try + { + m_inputLabel = m_formLayer.createControlAndShape( "FixedText", columnPos, rowPos, 70, 12, null ); + m_inputLabel.setPropertyValue( "MultiLine", Boolean.TRUE ); + + com.sun.star.awt.FontDescriptor font = (com.sun.star.awt.FontDescriptor)m_inputLabel.getPropertyValue( "FontDescriptor" ); + font.Weight = com.sun.star.awt.FontWeight.BOLD; + m_inputLabel.setPropertyValue( "FontDescriptor", font ); + + if ( controlHeight == 0 ) + controlHeight = 6; + + int controlPos = rowPos + 12; + XPropertySet[] controls = new XPropertySet[ controlCount ]; + for ( int i = 0; i < controlCount; ++i, controlPos += controlHeight ) + { + controls[ i ] = m_formLayer.createControlAndShape( formComponentService, columnPos, controlPos, 25, controlHeight, null ); + controls[ i ].setPropertyValue( "Name", formComponentService ); + controls[ i ].setPropertyValue( "Tag", String.valueOf( i ) ); + + if ( controls[ i ].getPropertySetInfo().hasPropertyByName( "Border" ) ) + controls[ i ].setPropertyValue( "Border", Short.valueOf( (short)2 ) ); + + XValidatableFormComponent xComp = UnoRuntime.queryInterface( XValidatableFormComponent.class, + controls[ i ] ); + xComp.addFormComponentValidityListener( this ); + } + m_inputField = controls[ 0 ]; + + + controlPos += 4; + XPropertySet xLabel = m_formLayer.createControlAndShape( "FixedText", columnPos, controlPos, 70, 4, null ); + xLabel.setPropertyValue( "Label", "Status:" ); + controlPos += 4; + m_statusField = m_formLayer.createControlAndShape( "FixedText", columnPos, controlPos, 70, 4, null ); + m_statusField.setPropertyValue( "Label", "" ); + + + controlPos += 6; + xLabel = m_formLayer.createControlAndShape( "FixedText", columnPos, controlPos, 70, 4, null ); + xLabel.setPropertyValue( "Label", "Explanation for invalidity:" ); + controlPos += 4; + m_explanationField = m_formLayer.createControlAndShape( "FixedText", columnPos, controlPos, 70, 4, null ); + m_explanationField.setPropertyValue( "Label", "" ); + + XValidatable xValidatable = UnoRuntime.queryInterface( XValidatable.class, m_inputField ); + xValidatable.setValidator( m_validator ); + } + catch( java.lang.Exception e ) + { + e.printStackTrace( System.err ); + } + } + + /* ------------------------------------------------------------------ */ + /* XEventListener overridables */ + /* ------------------------------------------------------------------ */ + public void disposing( com.sun.star.lang.EventObject eventObject ) + { + // not interested in + } + + /* ------------------------------------------------------------------ */ + /* XFormComponentValidityListener overridables */ + /* ------------------------------------------------------------------ */ + public void componentValidityChanged( com.sun.star.lang.EventObject eventObject ) + { + try + { + if ( m_inputField.equals( eventObject.Source ) ) + { + XValidatableFormComponent xComp = UnoRuntime.queryInterface( XValidatableFormComponent.class, + eventObject.Source ); + // the current value + Object value = xComp.getCurrentValue(); + + // the current validity flag + boolean isValid = xComp.isValid(); + + m_statusField.setPropertyValue("Label", isValid ? "valid" : "invalid" ); + m_statusField.setPropertyValue( "TextColor", Integer.valueOf( isValid ? 0x008000 : 0x800000 ) ); + + String validityMessage = ""; + if ( !isValid ) + validityMessage = m_validator.explainInvalid( value ); + m_explanationField.setPropertyValue( "Label", validityMessage ); + } + } + catch( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + } + } + +} diff --git a/forms/qa/integration/forms/SpreadsheetDocument.java b/forms/qa/integration/forms/SpreadsheetDocument.java new file mode 100644 index 000000000..2db38ab92 --- /dev/null +++ b/forms/qa/integration/forms/SpreadsheetDocument.java @@ -0,0 +1,68 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.lang.XComponent; +import com.sun.star.table.XCellRange; +import com.sun.star.container.XIndexAccess; +import com.sun.star.sheet.XSpreadsheetDocument; +import com.sun.star.sheet.XSpreadsheets; + +public class SpreadsheetDocument extends DocumentHelper +{ + /** Creates a new blank spreadsheet document */ + /* ------------------------------------------------------------------ */ + public SpreadsheetDocument( XMultiServiceFactory orb ) throws com.sun.star.uno.Exception + { + super( orb, implLoadAsComponent( orb, "private:factory/scalc" ) ); + } + + /* ------------------------------------------------------------------ */ + public SpreadsheetDocument( XMultiServiceFactory orb, XComponent document ) + { + super( orb, document ); + } + + /* ------------------------------------------------------------------ */ + /** returns the sheets collection + */ + public XSpreadsheets getSheets() + { + XSpreadsheetDocument spreadsheetDoc = UnoRuntime.queryInterface( XSpreadsheetDocument.class, + getDocument() + ); + return spreadsheetDoc.getSheets(); + } + + /* ------------------------------------------------------------------ */ + /** returns the sheet with the given index + */ + public XCellRange getSheet( int index ) throws com.sun.star.uno.Exception + { + XIndexAccess sheets = UnoRuntime.queryInterface( XIndexAccess.class, + getSheets() + ); + return UnoRuntime.queryInterface( XCellRange.class, + sheets.getByIndex( index ) + ); + } + +} diff --git a/forms/qa/integration/forms/SpreadsheetView.java b/forms/qa/integration/forms/SpreadsheetView.java new file mode 100644 index 000000000..22ff1c06b --- /dev/null +++ b/forms/qa/integration/forms/SpreadsheetView.java @@ -0,0 +1,59 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.lang.*; +import com.sun.star.frame.*; +import com.sun.star.sheet.*; +import com.sun.star.container.*; + +public class SpreadsheetView extends integration.forms.DocumentViewHelper +{ + + /** Creates a new instance of SpreadsheetView */ + public SpreadsheetView( XMultiServiceFactory orb, DocumentHelper document, XController controller ) + { + super( orb, document, controller ); + } + + /** activates the sheet with the given index + */ + void activateSheet( int sheetIndex ) + { + try + { + // get the sheet to activate + XSpreadsheetDocument doc = UnoRuntime.queryInterface( + XSpreadsheetDocument.class, getDocument().getDocument() ); + XIndexAccess sheets = UnoRuntime.queryInterface( + XIndexAccess.class, doc.getSheets() ); + + XSpreadsheet sheet = UnoRuntime.queryInterface( + XSpreadsheet.class, sheets.getByIndex( sheetIndex ) ); + + // activate + XSpreadsheetView view = UnoRuntime.queryInterface( + XSpreadsheetView.class, getController() ); + view.setActiveSheet( sheet ); + } + catch( com.sun.star.uno.Exception e ) + { + } + } +} diff --git a/forms/qa/integration/forms/TableCellTextBinding.java b/forms/qa/integration/forms/TableCellTextBinding.java new file mode 100644 index 000000000..7f745a8f7 --- /dev/null +++ b/forms/qa/integration/forms/TableCellTextBinding.java @@ -0,0 +1,192 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.table.XCell; +import com.sun.star.util.XModifyListener; +import com.sun.star.text.XTextRange; + +/** a value binding to be connected to a form control + + This binding synchronizes the text contained in a table cell (which you must + pass upon construction) to the text in an XBindableValue. + + Well, in real it does not synchronize both directions. The ValueBinding + service has not much room for own activity: It allows notification of changes + in the own value, and it allows external instances to set the current value. + + Note that we implement this binding as a separate thread, which is (more or + less permanently) polling for a new text at the cell. This is unfortunate, but + sadly the Writer table cells do not support actively notifying changes in their + content to other interested parties. +*/ +public class TableCellTextBinding + extends java.lang.Thread + implements com.sun.star.form.binding.XValueBinding, + com.sun.star.util.XModifyBroadcaster +{ + private final XTextRange m_cellText; + private Object m_writeSignal; + private String m_newCellText; + private String m_lastKnownCellText; + private boolean m_haveNewCellText; + private final java.util.List<com.sun.star.util.XModifyListener> m_listeners; + + /** Creates a new instance of TableCellTextBinding */ + public TableCellTextBinding( XCell cell ) + { + m_cellText = UnoRuntime.queryInterface( XTextRange.class, cell ); + + m_newCellText = ""; + m_listeners = new java.util.LinkedList<com.sun.star.util.XModifyListener>(); + + start(); + } + + /** retrieves the list of data types which this binding can exchange + */ + public com.sun.star.uno.Type[] getSupportedValueTypes() + { + try + { + // well, only strings here ... + return new Type[] { + getStringType() + }; + } + catch( java.lang.Exception e ) + { + } + return new Type[] { }; + } + + /** retrieves the current value + */ + public Object getValue(com.sun.star.uno.Type type) throws com.sun.star.form.binding.IncompatibleTypesException + { + if ( !type.equals( getStringType() ) ) + throw new com.sun.star.form.binding.IncompatibleTypesException(); + + return m_cellText.getString(); + } + + /** sets a new value + */ + public void setValue(Object obj) throws com.sun.star.form.binding.IncompatibleTypesException + { + String text; + try + { + text = (String)obj; + } + catch( java.lang.ClassCastException e ) + { + throw new com.sun.star.form.binding.IncompatibleTypesException(); + } + // remember the new text + // and wake up the thread which is waiting for it + synchronized( m_writeSignal ) + { + m_newCellText = text; + m_haveNewCellText = true; + m_writeSignal.notify(); + } + } + + /** determines whether a given value type is supported + */ + public boolean supportsType(com.sun.star.uno.Type type) + { + return type.equals( getStringType() ); + } + + /** retrieves the UNO type for the string class + */ + private static final Type getStringType() + { + return new com.sun.star.uno.Type( String.class ); + } + + /** runs the thread + */ + @Override + public void run() + { + try + { + m_writeSignal = new Object(); + while ( true ) + { + // go sleep a while + synchronized( m_writeSignal ) + { + m_writeSignal.wait( 200 ); + // if there's new text in the control, propagate it to the cell + if ( m_haveNewCellText ) + { + m_cellText.setString( m_newCellText ); + m_lastKnownCellText = m_newCellText; + } + m_haveNewCellText = false; + } + + // if there's new text in the cell, propagate it to the control + String currentCellText = m_cellText.getString(); + if ( !currentCellText.equals( m_lastKnownCellText ) ) + { + m_lastKnownCellText = currentCellText; + // notify the modification + synchronized( m_listeners ) + { + com.sun.star.lang.EventObject eventSource = new com.sun.star.lang.EventObject( this ); + + java.util.Iterator loop = m_listeners.iterator(); + while ( loop.hasNext() ) + { + ((XModifyListener)loop.next()).modified( eventSource ); + } + } + } + } + } + catch( java.lang.Exception e ) + { + e.printStackTrace(System.err); + } + } + + public void addModifyListener(com.sun.star.util.XModifyListener xModifyListener) + { + synchronized( m_listeners ) + { + m_listeners.add( xModifyListener ); + } + } + + public void removeModifyListener(com.sun.star.util.XModifyListener xModifyListener) + { + synchronized( m_listeners ) + { + m_listeners.remove( xModifyListener ); + } + } + +} diff --git a/forms/qa/integration/forms/TestCase.java b/forms/qa/integration/forms/TestCase.java new file mode 100644 index 000000000..cc1e6ec62 --- /dev/null +++ b/forms/qa/integration/forms/TestCase.java @@ -0,0 +1,150 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.util.XCloseable; +import com.sun.star.util.XModifiable; + +public abstract class TestCase extends complexlib.ComplexTestCase implements com.sun.star.lang.XEventListener +{ + protected XMultiServiceFactory m_orb; /// our service factory + private DocumentType m_documentType; /// the type of our document + protected DocumentHelper m_document; /// our current test document + protected FormLayer m_formLayer; + + /** Creates a new instance of TestCase */ + public TestCase( DocumentType docType ) + { + m_documentType = docType; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return this.getClass().getName(); + } + + /* ------------------------------------------------------------------ */ + public void before() throws java.lang.Exception + { + m_orb = param.getMSF(); + } + + /* ------------------------------------------------------------------ */ + public void after() throws java.lang.Exception + { + } + + /* ------------------------------------------------------------------ */ + /** closes our document, if we have an open one, via (simulated) user input + */ + protected void closeDocumentByUI() + { + try + { + if ( m_document != null ) + { + // first, set the document to "unmodified" + XModifiable docModify = m_document.query( XModifiable.class ); + docModify.setModified( false ); + + m_document.getCurrentView().dispatch( ".uno:CloseDoc" ); + + // CloseDoc is asynchronous, so wait until it's done - or 1 second, at most + synchronized ( this ) { wait( 1000 ); } + } + } + catch ( java.lang.Exception e ) + { + e.printStackTrace( System.err ); + } + } + + /* ------------------------------------------------------------------ */ + /** closes our document, if we have an open one + */ + protected void closeDocument() + { + try + { + // close our document + if ( m_document != null ) + { + XCloseable closeDoc = m_document.query( XCloseable.class ); + closeDoc.close( true ); + } + } + catch ( com.sun.star.uno.Exception e ) + { + e.printStackTrace( System.err ); + } + } + + /* ------------------------------------------------------------------ */ + /** prepares a new document to work with + */ + protected void prepareDocument() throws com.sun.star.uno.Exception, java.lang.Exception + { + m_document = DocumentHelper.blankDocument( m_orb, m_documentType ); + m_document.getDocument( ).addEventListener( this ); + m_formLayer = new FormLayer( m_document ); + } + + /* ------------------------------------------------------------------ */ + /* internal methods */ + /* ------------------------------------------------------------------ */ + /** waits for the user to press a key (on the console where she started the java program) + or the document to be closed by the user. + @return + <TRUE/> if the user pressed a key on the console, <FALSE/> if she closed the document + */ + protected boolean waitForUserInput() throws java.lang.Exception + { + synchronized (this) + { + integration.forms.WaitForInput aWait = new integration.forms.WaitForInput( this ); + aWait.start(); + wait(); + + // if the waiter thread is done, the user pressed enter + boolean bKeyPressed = aWait.isDone(); + if ( !bKeyPressed ) + aWait.interrupt(); + + return bKeyPressed; + } + } + + /* ------------------------------------------------------------------ */ + /* XEventListener overridables */ + /* ------------------------------------------------------------------ */ + public void disposing( com.sun.star.lang.EventObject eventObject ) + { + if ( m_document.getDocument().equals( eventObject.Source ) ) + { + // notify ourself that we can stop waiting for user input + synchronized (this) + { + notify(); + } + } + } +} diff --git a/forms/qa/integration/forms/TextValidator.java b/forms/qa/integration/forms/TextValidator.java new file mode 100644 index 000000000..0ed59dd41 --- /dev/null +++ b/forms/qa/integration/forms/TextValidator.java @@ -0,0 +1,65 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +public class TextValidator extends integration.forms.ControlValidator +{ + + public String explainInvalid( Object Value ) + { + try + { + String value = (String)Value; + if ( containsZs( value ) ) + return "No Z's allowed here"; + if ( !isProperChunks( value ) ) + return "Need 3 * n characters"; + } + catch( java.lang.Exception e ) + { + return "ooops. Unknown error"; + } + return ""; + } + + public boolean isValid( Object Value ) + { + try + { + String value = (String)Value; + if ( containsZs( value ) ) + return false; + return isProperChunks( value ); + } + catch( java.lang.Exception e ) + { + } + return false; + } + + private boolean isProperChunks( String value ) + { + return ( value.length() % 3 ) == 0; + } + + private boolean containsZs( String value ) + { + return ( value.indexOf( 'Z' ) != -1 ) + || ( value.indexOf( 'z' ) != -1 ); + } +} diff --git a/forms/qa/integration/forms/TimeValidator.java b/forms/qa/integration/forms/TimeValidator.java new file mode 100644 index 000000000..8ff19464a --- /dev/null +++ b/forms/qa/integration/forms/TimeValidator.java @@ -0,0 +1,73 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +public class TimeValidator extends integration.forms.ControlValidator +{ + + public String explainInvalid( Object Value ) + { + try + { + if ( isVoid( Value ) ) + return "empty input"; + + com.sun.star.util.Time timeValue = (com.sun.star.util.Time)Value; + if ( isInvalidTime( timeValue ) ) + return "this is no valid time"; + if ( !isFullHour( timeValue ) ) + return "time must denote a full hour"; + } + catch( java.lang.Exception e ) + { + return "this is no valid time"; + } + return ""; + } + + public boolean isValid( Object Value ) + { + try + { + if ( isVoid( Value ) ) + return false; + + com.sun.star.util.Time timeValue = (com.sun.star.util.Time) + com.sun.star.uno.AnyConverter.toObject( + com.sun.star.util.Time.class, Value); + if ( isInvalidTime( timeValue ) ) + return false; + return isFullHour( timeValue ); + } + catch( java.lang.Exception e ) + { + e.printStackTrace( System.err ); + } + return false; + } + + private boolean isInvalidTime( com.sun.star.util.Time timeValue ) + { + return ( timeValue.Hours == -1 ) && ( timeValue.Minutes == -1 ) && ( timeValue.Seconds == -1 ) && ( timeValue.NanoSeconds == -1 ); + } + + private boolean isFullHour( com.sun.star.util.Time timeValue ) + { + return ( timeValue.Minutes == 0 ) && ( timeValue.Seconds == 0 ) && ( timeValue.NanoSeconds == 0 ); + } +} diff --git a/forms/qa/integration/forms/ValueBinding.java b/forms/qa/integration/forms/ValueBinding.java new file mode 100644 index 000000000..3b6e66bdc --- /dev/null +++ b/forms/qa/integration/forms/ValueBinding.java @@ -0,0 +1,112 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +import com.sun.star.uno.UnoRuntime; + +import com.sun.star.beans.XPropertySet; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XText; +import com.sun.star.text.XTextTable; +import com.sun.star.text.XTextCursor; +import com.sun.star.form.binding.XValueBinding; +import com.sun.star.form.binding.XBindableValue; + +public class ValueBinding extends integration.forms.TestCase +{ + /** Creates a new instance of ValueBinding */ + public ValueBinding() + { + super( DocumentType.WRITER ); + } + + public static boolean isInteractiveTest() + { + return true; + } + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkBindingProperties" + }; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Form Control Value Binding Test"; + } + + /* ------------------------------------------------------------------ */ + @Override + public void before() throws com.sun.star.uno.Exception, java.lang.Exception + { + super.before(); + prepareDocument(); + } + + /* ------------------------------------------------------------------ */ + @Override + public void after() throws com.sun.star.uno.Exception, java.lang.Exception + { + super.waitForUserInput(); + super.closeDocument(); + } + + /* ------------------------------------------------------------------ */ + public void checkBindingProperties() throws java.lang.Exception + { + } + + /* ------------------------------------------------------------------ */ + @Override + protected void prepareDocument() throws com.sun.star.uno.Exception, java.lang.Exception + { + super.prepareDocument(); + + // insert a table with exactly one cell. The content of this table will be synced with + // the content of a form control + XTextDocument textDoc = UnoRuntime.queryInterface( XTextDocument.class, m_document.getDocument() ); + XText documentText = textDoc.getText(); + XTextCursor textCursor = documentText.createTextCursor(); + + XTextTable table = UnoRuntime.queryInterface( XTextTable.class, + m_document.createInstance( "com.sun.star.text.TextTable" ) + ); + table.initialize( 1, 1 ); + documentText.insertTextContent( textCursor, table, false ); + + // insert our sample control + XPropertySet textControl = m_formLayer.insertControlLine( "DatabaseTextField", "Test", "", 10 ); + + // create a value binding for the first cell of the table + XValueBinding cellBinding = new TableCellTextBinding( table.getCellByName( "A1" ) ); + // and bind it to the control + XBindableValue bindable = UnoRuntime.queryInterface( + XBindableValue.class, textControl + ); + bindable.setValueBinding( cellBinding ); + + // toggle the view to alive mode + m_document.getCurrentView( ).toggleFormDesignMode( ); + } + } diff --git a/forms/qa/integration/forms/WaitForInput.java b/forms/qa/integration/forms/WaitForInput.java new file mode 100644 index 000000000..c04c4dbe3 --- /dev/null +++ b/forms/qa/integration/forms/WaitForInput.java @@ -0,0 +1,58 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package integration.forms; + +class WaitForInput extends java.lang.Thread +{ + private final Object m_aToNotify; + private boolean m_bDone; + + public WaitForInput( Object aToNotify ) + { + m_aToNotify = aToNotify; + m_bDone = false; + } + + public boolean isDone() + { + return m_bDone; + } + + @Override + public void run() + { + try + { + System.out.println( "\npress enter to exit" ); + System.in.read(); + + m_bDone = true; + // notify that the user pressed the key + synchronized (m_aToNotify) + { + m_aToNotify.notify(); + } + } + catch( java.lang.Exception e ) + { + // not really interested in + System.err.println( e ); + } + } +} + diff --git a/forms/qa/integration/forms/XMLFormSettings.java b/forms/qa/integration/forms/XMLFormSettings.java new file mode 100644 index 000000000..8b393d094 --- /dev/null +++ b/forms/qa/integration/forms/XMLFormSettings.java @@ -0,0 +1,213 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.XPropertySet; +import com.sun.star.form.binding.IncompatibleTypesException; +import com.sun.star.form.binding.XBindableValue; +import com.sun.star.form.binding.XValueBinding; +import com.sun.star.frame.XStorable; +import com.sun.star.io.IOException; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.util.CloseVetoException; +import com.sun.star.util.XCloseable; +import com.sun.star.xml.dom.XNode; +import com.sun.star.xsd.DataTypeClass; +import java.io.File; +import org.openoffice.xforms.Instance; +import org.openoffice.xforms.Model; +import org.openoffice.xforms.XMLDocument; + +public class XMLFormSettings extends complexlib.ComplexTestCase +{ + private XMLDocument m_document; + private Model m_defaultModel; + private FormLayer m_formLayer; + private XPropertySet m_stringBinding; + private XPropertySet m_booleanBinding; + private XPropertySet m_dateBinding; + + /* ------------------------------------------------------------------ */ + @Override + public String[] getTestMethodNames() + { + return new String[] { + "checkExternalData" + }; + } + + /* ------------------------------------------------------------------ */ + @Override + public String getTestObjectName() + { + return "Form Control Spreadsheet Cell Binding Test"; + } + + /* ------------------------------------------------------------------ */ + public void before() throws java.lang.Exception + { + // create the document and assign related members + XMultiServiceFactory orb = param.getMSF(); + m_document = new XMLDocument( orb ); + m_formLayer = new FormLayer( m_document ); + + // create a simple structure in the DOM tree: an element with two attributes + String[] modelNames = m_document.getXFormModelNames(); + m_defaultModel = m_document.getXFormModel( modelNames[0] ); + final Instance defaultInstance = m_defaultModel.getDefaultInstance(); + // remove the default root node + defaultInstance.removeNode( "instanceData" ); + // create test structures + XNode stringElement = defaultInstance.createElement( "stringElement" ); + XNode booleanAttrib = defaultInstance.createAttribute( stringElement, "booleanAttribute", "true" ); + XNode dateAttrib = defaultInstance.createAttribute( stringElement, "dateAttribute" ); + + assure( "booleanAttrib's parent is wrong", + UnoRuntime.areSame( stringElement, booleanAttrib.getParentNode() ) ); + assure( "dateAttrib's parent is wrong", + UnoRuntime.areSame( stringElement, dateAttrib.getParentNode() ) ); + + // also create bindings for the element and its attributes, of the proper type + m_stringBinding = m_defaultModel.createBindingForNode( stringElement, DataTypeClass.STRING ); + m_booleanBinding = m_defaultModel.createBindingForNode( booleanAttrib, DataTypeClass.BOOLEAN ); + m_dateBinding = m_defaultModel.createBindingForNode( dateAttrib, DataTypeClass.DATE ); + + // TODO: set up the bindings so that the date bindings is relevant if and only if + // the boolean value is true + + // store the document + File tempFile = File.createTempFile( "xmlforms", ".odt" ); + tempFile.deleteOnExit(); + String fileURL = tempFile.toURI().toURL().toExternalForm(); + XStorable store = UnoRuntime.queryInterface( XStorable.class, + m_document.getDocument() ); + store.storeAsURL( fileURL, new PropertyValue[] {} ); + assure( "document still modified after saving it", !m_document.isModified() ); + } + + /* ------------------------------------------------------------------ */ + public void after() throws com.sun.star.uno.Exception, java.lang.Exception + { + impl_closeDocument(); + } + + /* ------------------------------------------------------------------ */ + private void impl_closeDocument() throws CloseVetoException + { + if ( m_document != null ) + { + XCloseable closeDoc = UnoRuntime.queryInterface( XCloseable.class, + m_document.getDocument() ); + closeDoc.close( true ); + } + } + + /* ------------------------------------------------------------------ */ + private static void impl_bind( XPropertySet _control, XPropertySet _binding ) throws IncompatibleTypesException + { + XBindableValue bindableControl = UnoRuntime.queryInterface( + XBindableValue.class, _control ); + XValueBinding binding = UnoRuntime.queryInterface( + XValueBinding.class, _binding ); + bindableControl.setValueBinding( binding ); + } + + /* ------------------------------------------------------------------ */ + /** checks if master-detail relationships including multiple keys work + */ + public void checkExternalData() throws com.sun.star.uno.Exception, java.lang.Exception + { + // some controls + XPropertySet stringControl = m_formLayer.createLabeledControl( + "DatabaseTextField", "Task", 10, 10, 6 ); + impl_bind( stringControl, m_stringBinding ); + + XPropertySet booleanControl = m_formLayer.createControlAndShape( + "DatabaseCheckBox", 35, 18, 25, 6 ); + booleanControl.setPropertyValue( "Label", "has due date" ); + impl_bind( booleanControl, m_booleanBinding ); + + XPropertySet dateControl = m_formLayer.createControlAndShape( + "DatabaseDateField", 40, 26, 25, 6 ); + dateControl.setPropertyValue( "Dropdown", Boolean.TRUE ); + impl_bind( dateControl, m_dateBinding ); + + m_document.getCurrentView( ).toggleFormDesignMode( ); + + // ensure the model is set up as containing "document-internal" data + m_defaultModel.setIsDocumentInternalData( true ); + assure( "setting up the document to contain 'internal data' failed", + m_defaultModel.getIsDocumentInternalData() ); + impl_storeDocument(); + + // okay, here we go ... + // what this particular test is about is to check whether we can set up the model + // so that any changes to any controls bound to any data in this model actually marks + // the containing document as modified + m_formLayer.userTextInput( stringControl, "don't break this test" ); + assure( "model data changed, but document is not modified", + m_document.isModified() ); + + // TODO: do this with the other control/binding types, too + + // no the other way round: set up the model to contain "document-external" data + m_defaultModel.setIsDocumentInternalData( false ); + assure( "setting up the document to contain 'internal data' failed", + !m_defaultModel.getIsDocumentInternalData() ); + impl_storeDocument(); + + // and check that now, changes in the controls / model data are not reflected in + // document's modified state + m_formLayer.userTextInput( stringControl, "(or any other test, that is)" ); + assure( "model data changed, but document is modified", + !m_document.isModified() ); + + + // finally, check whether the flag survives loading and saving + Model internalDataModel = m_document.addXFormModel( "internalData" ); + internalDataModel.setIsDocumentInternalData( true ); + Model externalDataModel = m_document.addXFormModel( "externalData" ); + externalDataModel.setIsDocumentInternalData( false ); + + impl_storeDocument(); + m_document.reload(); + + internalDataModel = m_document.getXFormModel( "internalData" ); + externalDataModel = m_document.getXFormModel( "externalData" ); + + assure( "setting up a model to contain 'internal data' did not survive reloading", + internalDataModel.getIsDocumentInternalData() ); + assure( "setting up a model to contain 'external data' did not survive reloading", + !externalDataModel.getIsDocumentInternalData() ); + } + + /* ------------------------------------------------------------------ */ + /** stores our document + * @throws com.sun.star.io.IOException + */ + private void impl_storeDocument() throws IOException + { + XStorable store = UnoRuntime.queryInterface( XStorable.class, + m_document.getDocument() ); + store.store(); + assure( "document still modified after saving it", !m_document.isModified() ); + } +} diff --git a/forms/qa/integration/forms/dbfTools.java b/forms/qa/integration/forms/dbfTools.java new file mode 100644 index 000000000..fe61c6ef4 --- /dev/null +++ b/forms/qa/integration/forms/dbfTools.java @@ -0,0 +1,73 @@ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package integration.forms; + +import com.sun.star.uno.*; +import com.sun.star.lang.*; +import com.sun.star.beans.*; +import com.sun.star.container.*; + +/** provides global helpers +*/ +class dbfTools +{ + /* ------------------------------------------------------------------ */ + /** disposes the component given + */ + public static void disposeComponent( Object xComp ) throws java.lang.RuntimeException + { + XComponent xComponent = queryComponent( xComp ); + if ( null != xComponent ) + xComponent.dispose(); + } + + /* ------------------------------------------------------------------ */ + /** queries an object for the XPropertySet interface + */ + public static XPropertySet queryPropertySet( Object aComp ) + { + return UnoRuntime.queryInterface( XPropertySet.class, aComp ); + } + + /* ------------------------------------------------------------------ */ + /** queries an object for the XIndexContainer interface + */ + public static XIndexContainer queryIndexContainer( Object aComp ) + { + return UnoRuntime.queryInterface( XIndexContainer.class, aComp ); + } + + /* ------------------------------------------------------------------ */ + /** queries an object for the XComponent interface + */ + public static XComponent queryComponent( Object aComp ) + { + return UnoRuntime.queryInterface( XComponent.class, aComp ); + } + + /* ------------------------------------------------------------------ */ + /** retrieves the parent of the given object + */ + @SuppressWarnings("unchecked") + static Object getParent( Object aComponent, Class aInterfaceClass ) + { + XChild xAsChild = UnoRuntime.queryInterface( XChild.class, aComponent ); + return UnoRuntime.queryInterface( aInterfaceClass, xAsChild.getParent() ); + } +} |