1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package complex.toolkit;
25 
26 import com.sun.star.awt.XControl;
27 import com.sun.star.awt.XControlContainer;
28 import com.sun.star.awt.XControlModel;
29 import com.sun.star.awt.XToolkit;
30 import com.sun.star.awt.grid.DefaultGridDataModel;
31 import com.sun.star.awt.grid.XGridColumn;
32 import com.sun.star.awt.grid.XGridColumnModel;
33 import com.sun.star.awt.grid.XGridControl;
34 import com.sun.star.awt.grid.XGridDataModel;
35 import com.sun.star.awt.grid.XMutableGridDataModel;
36 import com.sun.star.awt.grid.XSortableMutableGridDataModel;
37 import com.sun.star.beans.Pair;
38 import com.sun.star.beans.XPropertySet;
39 import com.sun.star.container.ContainerEvent;
40 import com.sun.star.container.XContainerListener;
41 import com.sun.star.container.XNameContainer;
42 import com.sun.star.lang.EventObject;
43 import com.sun.star.lang.IndexOutOfBoundsException;
44 import com.sun.star.lang.XComponent;
45 import com.sun.star.lang.XEventListener;
46 import com.sun.star.lang.XMultiServiceFactory;
47 import com.sun.star.uno.Exception;
48 import com.sun.star.uno.UnoRuntime;
49 import com.sun.star.uno.XComponentContext;
50 import com.sun.star.uno.XInterface;
51 import com.sun.star.util.XCloneable;
52 import complex.toolkit.awtgrid.DummyColumn;
53 import complex.toolkit.awtgrid.TMutableGridDataModel;
54 import java.lang.reflect.Method;
55 import java.util.ArrayList;
56 import java.util.List;
57 import java.util.Random;
58 import org.junit.AfterClass;
59 import org.junit.BeforeClass;
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Test;
63 import static org.junit.Assert.*;
64 import org.openoffice.test.OfficeConnection;
65 
66 /** is a unit test for the grid control related implementations
67  * @author frank.schoenheit@sun.com
68  */
69 public class GridControl
70 {
71     // -----------------------------------------------------------------------------------------------------------------
GridControl()72     public GridControl()
73     {
74         m_context = m_connection.getComponentContext();
75     }
76 
77     // -----------------------------------------------------------------------------------------------------------------
impl_dispose( final Object... i_components )78     private static void impl_dispose( final Object... i_components )
79     {
80         for ( int i=0; i<i_components.length; ++i )
81         {
82             if ( i_components[i] != null )
83             {
84                 final XComponent component = UnoRuntime.queryInterface( XComponent.class, i_components[i] );
85                 component.dispose();
86             }
87         }
88     }
89 
90     // -----------------------------------------------------------------------------------------------------------------
impl_recreateGridModel()91     private void impl_recreateGridModel() throws Exception
92     {
93         impl_dispose( m_gridControlModel, m_columnModel, m_dataModel );
94 
95         // create a grid control model, and ensure it has a proper data and column model already
96         m_gridControlModel = UnoRuntime.queryInterface( XPropertySet.class,
97             createInstance( "com.sun.star.awt.grid.UnoControlGridModel" ) );
98         assertNotNull( "grid control model does not provide XPropertySet interface", m_gridControlModel );
99 
100         // ensure that the model has default column/data models
101         m_columnModel = UnoRuntime.queryInterface( XGridColumnModel.class, m_gridControlModel.getPropertyValue( "ColumnModel" ) );
102         assertNotNull( "the control model is expected to have an initial column model", m_columnModel );
103         final XGridDataModel dataModel = UnoRuntime.queryInterface( XGridDataModel.class, m_gridControlModel.getPropertyValue( "GridDataModel" ) );
104         assertNotNull( "the control model is expected to have an initial data model", dataModel );
105         m_dataModel = UnoRuntime.queryInterface( XSortableMutableGridDataModel.class,
106             dataModel );
107         assertNotNull( "the out-of-the-box data model should be mutable and sortable", m_dataModel );
108     }
109 
110     // -----------------------------------------------------------------------------------------------------------------
111     @Test
testGridControlCloning()112     public void testGridControlCloning() throws Exception
113     {
114         impl_recreateGridModel();
115 
116         // give the test something to compare, actually
117         XGridColumnModel columnModel = UnoRuntime.queryInterface( XGridColumnModel.class,
118             m_gridControlModel.getPropertyValue( "ColumnModel" ) );
119         columnModel.setDefaultColumns( 10 );
120 
121         // clone the grid model
122         final XCloneable cloneable = UnoRuntime.queryInterface( XCloneable.class, m_gridControlModel );
123         assertNotNull( "all UnoControlModel's are expected to be cloneable", cloneable );
124 
125         final XInterface clone = cloneable.createClone();
126         final XPropertySet clonedProps = UnoRuntime.queryInterface( XPropertySet.class, clone );
127 
128         // TODO: check all those generic properties for equality
129 
130         // the data model and the column model should have been cloned, too
131         // in particular, the clone should not share the sub models with the orignal
132         final XMutableGridDataModel originalDataModel = UnoRuntime.queryInterface( XMutableGridDataModel.class,
133             m_gridControlModel.getPropertyValue( "GridDataModel" ) );
134         final XMutableGridDataModel clonedDataModel = UnoRuntime.queryInterface( XMutableGridDataModel.class,
135             clonedProps.getPropertyValue( "GridDataModel" ) );
136         assertFalse( "data model should not be shared after cloning", UnoRuntime.areSame( originalDataModel, clonedDataModel ) );
137         impl_assertEquality( originalDataModel, clonedDataModel );
138 
139         final XGridColumnModel originalColumnModel = columnModel;
140         final XGridColumnModel clonedColumnModel = UnoRuntime.queryInterface( XGridColumnModel.class,
141             clonedProps.getPropertyValue( "ColumnModel" ) );
142         assertFalse( "column model should not be shared after cloning", UnoRuntime.areSame( originalColumnModel, clonedColumnModel ) );
143         impl_assertEquality( originalColumnModel, clonedColumnModel );
144     }
145 
146     // -----------------------------------------------------------------------------------------------------------------
147     @Test
testDisposal()148     public void testDisposal() throws Exception
149     {
150         impl_recreateGridModel();
151 
152         final int columnCount = 3;
153         m_columnModel.setDefaultColumns( columnCount );
154 
155         // add disposal listeners to all columns so far
156         final XGridColumn[] columns = m_columnModel.getColumns();
157         assertEquals( "creating default columns resulted in unexpected column count", columnCount, columns.length );
158         final DisposeListener[] columnListeners = new DisposeListener[columnCount];
159         for ( int i=0; i<columnCount; ++i )
160             columnListeners[i] = new DisposeListener( columns[i] );
161 
162         // add another column, and check that upon removal, it is disposed
163         final int newColumnIndex = m_columnModel.addColumn( m_columnModel.createColumn() );
164         final DisposeListener columnListener = new DisposeListener( m_columnModel.getColumn( newColumnIndex ) );
165         m_columnModel.removeColumn( newColumnIndex );
166         assertTrue( "explicit column removal is expected to dispose the column", columnListener.isDisposed() );
167 
168         // by definition, the grid control model is the owner of both the column and the data model. So, setting
169         // a new column/data model should implicitly dispose the old models
170         final DisposeListener oldDataModelListener = new DisposeListener( m_dataModel );
171         final DisposeListener oldColumnModelListener = new DisposeListener( m_columnModel );
172 
173         final Object newDataModel = createInstance( "com.sun.star.awt.grid.DefaultGridDataModel" );
174         final Object newColumnModel = createInstance( "com.sun.star.awt.grid.DefaultGridColumnModel" );
175         final DisposeListener newDataModelListener = new DisposeListener( newDataModel );
176         final DisposeListener newColumnModelListener = new DisposeListener( newColumnModel );
177 
178         m_gridControlModel.setPropertyValue( "GridDataModel", newDataModel );
179         assertTrue( "setting a new data model failed", impl_areSameInterface( newDataModel, m_gridControlModel.getPropertyValue( "GridDataModel" ) ) );
180         m_gridControlModel.setPropertyValue( "ColumnModel", newColumnModel );
181         assertTrue( "setting a new column model failed", impl_areSameInterface( newColumnModel, m_gridControlModel.getPropertyValue( "ColumnModel" ) ) );
182 
183         assertTrue( "old data model has not been disposed", oldDataModelListener.isDisposed() );
184         assertTrue( "old column model has not been disposed", oldColumnModelListener.isDisposed() );
185         for ( int i=0; i<columnCount; ++i )
186             assertTrue( "column no. " + i + " has not been disposed", columnListeners[i].isDisposed() );
187 
188         // the same holds if the grid control model itself is disposed - it should dispose the depending models, too
189         assertFalse( "new data model is already disposed - this is unexpected", newDataModelListener.isDisposed() );
190         assertFalse( "new column model is already disposed - this is unexpected", newColumnModelListener.isDisposed() );
191         impl_dispose( m_gridControlModel );
192         assertTrue( "new data model is not disposed after disposing the grid column model", newDataModelListener.isDisposed() );
193         assertTrue( "new column model is not disposed after disposing the grid column model", newColumnModelListener.isDisposed() );
194     }
195 
196     // -----------------------------------------------------------------------------------------------------------------
197     /**
198      * tests various aspects of the <code>XMutableGridDataModel</code> interface
199      */
200     @Test
testMutableGridDataModel()201     public void testMutableGridDataModel() throws Exception
202     {
203         impl_recreateGridModel();
204 
205         TMutableGridDataModel test = new TMutableGridDataModel( m_dataModel );
206         test.testAddRow();
207         test.testAddRows();
208         test.testInsertRow();
209         test.testInsertRows();
210         test.testRemoveRow();
211         test.testRemoveAllRows();
212         test.testUpdateCellData();
213         test.testUpdateRowData();
214         test.testUpdateRowHeading();
215         test.cleanup();
216 
217         // a somehwat less straight-forward test: the data model is expected to implicitly increase its column count
218         // when you add a row which has more columns than currently known
219         final XMutableGridDataModel dataModel = DefaultGridDataModel.create( m_context );
220         dataModel.addRow( 0, new Object[] { 1 } );
221         assertEquals( "unexpected column count after adding the most simple row", 1, dataModel.getColumnCount() );
222         dataModel.addRow( 1, new Object[] { 1, 2 } );
223         assertEquals( "implicit extension of the column count doesn't work", 2, dataModel.getColumnCount() );
224     }
225 
226     // -----------------------------------------------------------------------------------------------------------------
227     @Test
testGridColumnModel()228     public void testGridColumnModel() throws Exception
229     {
230         impl_recreateGridModel();
231 
232         ColumnModelListener listener = new ColumnModelListener();
233         m_columnModel.addContainerListener( listener );
234 
235         // insert default columns into the previously empty model, ensure we get the right notifications
236         final int defaultColumnsCount = 3;
237         m_columnModel.setDefaultColumns( defaultColumnsCount );
238         impl_assertColumnModelConsistency();
239         List< ContainerEvent > events = listener.assertExclusiveInsertionEvents();
240         listener.reset();
241         assertEquals( "wrong number of events fired by setDefaulColumns", defaultColumnsCount, events.size() );
242         for ( int i=0; i<defaultColumnsCount; ++i )
243         {
244             final ContainerEvent event = events.get(i);
245             final int index = impl_assertInteger( event.Accessor );
246             assertEquals( "unexpected Accessor value in insert notification", i, index );
247             assertTrue( "wrong column object notified in insert notification",
248                 impl_areSameInterface( event.Element, m_columnModel.getColumn(i) ) );
249         }
250 
251         // insert some more default columns, ensure that all previously existing columns are removed
252         final int moreDefaultColumnsCount = 5;
253         m_columnModel.setDefaultColumns( moreDefaultColumnsCount );
254         impl_assertColumnModelConsistency();
255         assertEquals( "setting default columns is expected to remove all previously existing columns",
256             moreDefaultColumnsCount, m_columnModel.getColumnCount() );
257 
258         // in this situation, both removal and insertion events have been notified
259         final List< ContainerEvent > removalEvents = listener.getRemovalEvents();
260         final List< ContainerEvent > insertionEvents = listener.getInsertionEvents();
261         listener.reset();
262 
263         // for the removal events, check the indexes
264         assertEquals( "wrong number of columns removed (or notified) upon setting default columns",
265             defaultColumnsCount, removalEvents.size() );
266         for ( int i=0; i<removalEvents.size(); ++i )
267         {
268             final ContainerEvent event = removalEvents.get(i);
269             final int removedIndex = impl_assertInteger( event.Accessor );
270 
271             // The implementation is allowed to remove the columns from the beginning, in which case the
272             // index of the removed column must always be 0, since e.g. the second column has index 0
273             // after the first column (which previously had index 0) had been removed.
274             // Alternatively, the implementation is allowed to remove columns from the end, which means
275             // that the column index given in the event is steadily increasing.
276             assertTrue( "unexpected column removal event column index",
277                 ( removedIndex == 0 ) || ( removedIndex == removalEvents.size() - 1 - i ) );
278         }
279 
280         // for the insertion events, check the indexes as well
281         assertEquals( "wrong number of insertion events when setting default columns over existing columns",
282             moreDefaultColumnsCount, insertionEvents.size() );
283         for ( int i=0; i<insertionEvents.size(); ++i )
284         {
285             final ContainerEvent event = insertionEvents.get(i);
286             final int index = impl_assertInteger( event.Accessor );
287             assertEquals( i, index );
288         }
289 
290         // okay, remove all those columns
291         while ( m_columnModel.getColumnCount() != 0 )
292         {
293             final int columnCount = m_columnModel.getColumnCount();
294             final int removeColumnIndex = m_randomGenerator.nextInt( columnCount );
295             m_columnModel.removeColumn( removeColumnIndex );
296             events = listener.assertExclusiveRemovalEvents();
297             listener.reset();
298             assertEquals( "removing a single column should notify a single event", 1, events.size() );
299             final ContainerEvent event = events.get(0);
300             final int removalIndex = impl_assertInteger( event.Accessor );
301             assertEquals( "removing an arbitrary column does not notify the proper accessor",
302                 removeColumnIndex, removalIndex );
303         }
304 
305         // calling addColumn with a column not created by the given model/implementatoion should not succeed
306         boolean caughtExpected = false;
307         try
308         {
309             m_columnModel.addColumn( new DummyColumn() );
310         }
311         catch( final com.sun.star.lang.IllegalArgumentException e )
312         {
313             assertTrue( impl_areSameInterface( e.Context, m_columnModel ) );
314             caughtExpected = true;
315         }
316         assertTrue( "adding a dummy (self-implemented) grid column to the model should not succeed", caughtExpected );
317 
318         // adding a single column to the end should succeed, properly notify, and still be consistent
319         final XGridColumn newColumn = m_columnModel.createColumn();
320         m_columnModel.addColumn( newColumn );
321         impl_assertColumnModelConsistency();
322         events = listener.assertExclusiveInsertionEvents();
323         listener.reset();
324         assertEquals( "addColumn notifies the wrong number of insertion events", 1, events.size() );
325         final int insertionIndex = impl_assertInteger( events.get(0).Accessor );
326         assertEquals( insertionIndex, newColumn.getIndex() );
327     }
328 
329     // -----------------------------------------------------------------------------------------------------------------
330     @Test
testDataModel()331     public void testDataModel() throws Exception
332     {
333         impl_recreateGridModel();
334 
335         // ensure that getCellData and getRowData have the same opinion on the data they deliver
336         final Object[][] data = new Object[][] {
337             new Object[] { 15, 17, 0 },
338             new Object[] { 9, 8, 14 },
339             new Object[] { 17, 2, 16 },
340             new Object[] { 0, 7, 14 },
341             new Object[] { 10, 16, 16 },
342         };
343         m_dataModel.addRows( new Object[ data.length ], data );
344 
345         for ( int row = 0; row < data.length; ++row )
346         {
347             assertArrayEquals( "getRowData delivers wrong data in row " + row, data[row], m_dataModel.getRowData( row ) );
348             for ( int col = 0; col < data[row].length; ++col )
349             {
350                 assertEquals( "getCellData delivers wrong data at position (" + col + ", " + row + ")",
351                         data[row][col], m_dataModel.getCellData( col, row ) );
352             }
353         }
354     }
355 
356     // -----------------------------------------------------------------------------------------------------------------
357     @Test
testSortableDataModel()358     public void testSortableDataModel() throws Exception
359     {
360         impl_recreateGridModel();
361 
362         final int colCount = 3;
363         final int rowCount = 10;
364         // initialize with some data
365         final Object[][] data = new Object[][] {
366             new Object[] { 15, 17, 0 },
367             new Object[] { 9, 8, 14 },
368             new Object[] { 17, 2, 16 },
369             new Object[] { 0, 7, 14 },
370             new Object[] { 10, 16, 16 },
371             new Object[] { 2, 8, 10 },
372             new Object[] { 4, 8, 3 },
373             new Object[] { 7, 9, 0 },
374             new Object[] { 15, 6, 19 },
375             new Object[] { 2, 14, 19 }
376         };
377         final Object[] rowHeadings = new Object[] {
378             0, 1, 2, 3, 4, 5, 6, 7, 8, 9
379         };
380         // ensure consistency of the test data
381         assertEquals( rowHeadings.length, rowCount );
382         assertEquals( data.length, rowCount );
383         for ( Object[] rowData : data )
384             assertEquals( rowData.length, colCount );
385 
386         // add the test data
387         m_dataModel.addRows( rowHeadings, data );
388         assertEquals( rowCount, m_dataModel.getRowCount() );
389         assertEquals( colCount, m_dataModel.getColumnCount() );
390 
391         // sort by each column
392         for ( int colIndex = 0; colIndex < colCount; ++colIndex )
393         {
394             for ( boolean ascending : new boolean[] { true, false } )
395             {
396                 m_dataModel.sortByColumn( colIndex, ascending );
397                 Pair currentSortOrder = m_dataModel.getCurrentSortOrder();
398                 assertEquals( "invalid current sort column (column " + colIndex + ")", ((Integer)currentSortOrder.First).intValue(), colIndex );
399                 assertEquals( "invalid current sort direction", ((Boolean)currentSortOrder.Second).booleanValue(), ascending );
400 
401                 /*for ( int i=0; i<rowCount; ++i )
402                 {
403                     for ( int j=0; j<colCount; ++j )
404                         System.out.print( m_dataModel.getCellData( j, i ).toString() + ", " );
405                     System.out.println();
406                 }*/
407 
408                 // verify the data is actually sorted by this column
409                 for ( int rowIndex = 0; rowIndex < rowCount - 1; ++rowIndex )
410                 {
411                     final Object currentValue = m_dataModel.getCellData( colIndex, rowIndex );
412                     final int currentIntValue = impl_assertInteger( currentValue );
413                     final Object nextValue = m_dataModel.getCellData( colIndex, rowIndex + 1 );
414                     final int nextIntValue = impl_assertInteger( nextValue );
415                     assertTrue( "data in row " + rowIndex + " is actually not sorted " + ( ascending ? "ascending" : "descending" ),
416                         ascending   ? currentIntValue <= nextIntValue
417                                     : currentIntValue >= nextIntValue );
418 
419                     // ensure the data in the other columns, and the row headings, are sorted as well
420                     final Object rowHeading = m_dataModel.getRowHeading( rowIndex );
421                     final int unsortedRowIndex = impl_assertInteger( rowHeading );
422                     for ( int innerColIndex = 0; innerColIndex < colCount; ++innerColIndex )
423                     {
424                         assertEquals( "sorted row " + rowIndex + ", unsorted row " + unsortedRowIndex + ", col " + innerColIndex +
425                             ": wrong data",
426                             data[unsortedRowIndex][innerColIndex], m_dataModel.getCellData( innerColIndex, rowIndex ) );
427                     }
428                 }
429             }
430         }
431     }
432 
433     // -----------------------------------------------------------------------------------------------------------------
434     @Test
testView()435     public void testView() throws Exception
436     {
437         final XControl control = impl_createDialogWithGridControl();
438         final XPropertySet gridModelProps =
439             UnoRuntime.queryInterface( XPropertySet.class, control.getModel() );
440 
441         // in the current implementation (not sure this is a good idea at all), the control (more precise: the peer)
442         // ensures that if there are no columns in the column model, but in the data model, then the column model
443         // will implicitly have the needed columns added.
444         // To ensure that clients which rely on this do not break in the future, check this here.
445         final XMutableGridDataModel dataModel = UnoRuntime.queryInterface( XMutableGridDataModel.class,
446             gridModelProps.getPropertyValue( "GridDataModel" ) );
447         assertNotNull( dataModel );
448         assertEquals( 0, dataModel.getColumnCount() );
449 
450         final XGridColumnModel columnModel = UnoRuntime.queryInterface( XGridColumnModel.class,
451             gridModelProps.getPropertyValue( "ColumnModel" ) );
452         assertNotNull( columnModel );
453         assertEquals( 0, columnModel.getColumnCount() );
454 
455         final int columnCount = 3;
456         final int rowCount = 2;
457         dataModel.addRow( null, new Object[] { 1, 2, 3 } );
458         dataModel.addRow( null, new Object[] { 6, 5, 4 } );
459 
460         assertEquals( columnCount, dataModel.getColumnCount() );
461         assertEquals( columnCount, columnModel.getColumnCount() );
462 
463         // some cursor traveling
464         final XGridControl gridControl = UnoRuntime.queryInterface( XGridControl.class, control );
465         gridControl.goToCell( 0, 0 );
466         assertEquals( "wrong 'current column' (1)", 0, gridControl.getCurrentColumn() );
467         assertEquals( "wrong 'current row' (1)", 0, gridControl.getCurrentRow() );
468         gridControl.goToCell( columnCount - 1, rowCount - 1 );
469         assertEquals( "wrong 'current column' (2)", dataModel.getColumnCount() - 1, gridControl.getCurrentColumn() );
470         assertEquals( "wrong 'current row' (2)", dataModel.getRowCount() - 1, gridControl.getCurrentRow() );
471 
472         // removing the last column, while the active cell is in this very last column, is expected to adjust
473         // the active cell
474         columnModel.removeColumn( columnCount - 1 );
475         assertEquals( "removed the last and active column, active column was not adjusted!",
476             columnCount - 2, gridControl.getCurrentColumn() );
477         // same holds for rows
478         dataModel.removeRow( rowCount - 1 );
479         assertEquals( "removed the last and active row, active row was not adjusted!",
480             rowCount - 2, gridControl.getCurrentRow() );
481     }
482 
483     // -----------------------------------------------------------------------------------------------------------------
impl_createDialogWithGridControl()484     private XControl impl_createDialogWithGridControl() throws Exception
485     {
486         // create a simple dialog model/control/peer trinity
487         final XControlModel dialogModel = createInstance( XControlModel.class, "com.sun.star.awt.UnoControlDialogModel" );
488         m_disposables.add( dialogModel );
489         final XPropertySet dialogProps = UnoRuntime.queryInterface( XPropertySet.class, dialogModel );
490         dialogProps.setPropertyValue( "Width", 200 );
491         dialogProps.setPropertyValue( "Height", 100 );
492         dialogProps.setPropertyValue( "Title", "Grid Control Unit Test" );
493         final XControl dialogControl = createInstance( XControl.class, "com.sun.star.awt.UnoControlDialog" );
494         m_disposables.add( dialogControl );
495         dialogControl.setModel( dialogModel );
496         dialogControl.createPeer( createInstance( XToolkit.class, "com.sun.star.awt.Toolkit" ), null );
497 
498         // insert a grid control model
499         final XMultiServiceFactory controlModelFactory = UnoRuntime.queryInterface( XMultiServiceFactory.class,
500             dialogModel );
501         final XPropertySet gridModelProps = UnoRuntime.queryInterface( XPropertySet.class,
502             controlModelFactory.createInstance( "com.sun.star.awt.grid.UnoControlGridModel" ) );
503         m_disposables.add( gridModelProps );
504         gridModelProps.setPropertyValue( "PositionX", 6 );
505         gridModelProps.setPropertyValue( "PositionY", 6 );
506         gridModelProps.setPropertyValue( "Width", 188 );
507         gridModelProps.setPropertyValue( "Height", 88 );
508         final XNameContainer modelContainer = UnoRuntime.queryInterface( XNameContainer.class, dialogModel );
509         modelContainer.insertByName( "grid", gridModelProps );
510 
511         // check the respective control has been created
512         final XControlContainer controlContainer = UnoRuntime.queryInterface( XControlContainer.class, dialogControl );
513         final XControl gridControl = controlContainer.getControl( "grid" );
514         assertNotNull( "no grid control created in the dialog", gridControl );
515 
516         return gridControl;
517     }
518 
519     // -----------------------------------------------------------------------------------------------------------------
impl_assertInteger( final Object i_object )520     private int impl_assertInteger( final Object i_object )
521     {
522         assertTrue( i_object instanceof Integer );
523         return ((Integer)i_object).intValue();
524     }
525 
526     // -----------------------------------------------------------------------------------------------------------------
impl_assertColumnModelConsistency()527     private void impl_assertColumnModelConsistency() throws IndexOutOfBoundsException
528     {
529         for ( int col = 0; col < m_columnModel.getColumnCount(); ++col )
530         {
531             final XGridColumn column = m_columnModel.getColumn( col );
532             assertNotNull( column );
533             assertEquals( "column/model inconsistency: column " + col + " has a wrong index!", col, column.getIndex() );
534         }
535 
536         final XGridColumn[] allColumns = m_columnModel.getColumns();
537         assertEquals( "getColumns returns the wrong number of column objects",
538             m_columnModel.getColumnCount(), allColumns.length );
539         for ( int col = 0; col < m_columnModel.getColumnCount(); ++col )
540         {
541             assertTrue( "getColumns inconsistency", impl_areSameInterface( allColumns[col], m_columnModel.getColumn(col) ) );
542         }
543     }
544 
545     // -----------------------------------------------------------------------------------------------------------------
impl_assertEquality( final XGridDataModel i_reference, final XGridDataModel i_compare )546     private void impl_assertEquality( final XGridDataModel i_reference, final XGridDataModel i_compare ) throws IndexOutOfBoundsException
547     {
548         assertNotNull( i_reference );
549         assertNotNull( i_compare );
550 
551         assertEquals( "data model comparison: wrong column counts", i_reference.getColumnCount(), i_compare.getColumnCount() );
552         assertEquals( "data model comparison: wrong row counts", i_reference.getRowCount(), i_compare.getRowCount() );
553 
554         for ( int row = 0; row < i_reference.getRowCount(); ++row )
555         {
556             assertEquals( "data model comparison: wrong row heading content in row " + row,
557                     i_reference.getRowHeading( row ) );
558             for ( int col = 0; col < i_reference.getRowCount(); ++col )
559             {
560                 assertEquals( "data model comparison: wrong cell content in cell (" + col + ", " + row + ")",
561                     i_reference.getCellData( col, row ) );
562                 assertEquals( "data model comparison: wrong tooltip content in cell (" + col + ", " + row + ")",
563                     i_reference.getCellToolTip( col, row ) );
564             }
565         }
566     }
567 
568     // -----------------------------------------------------------------------------------------------------------------
impl_assertEquality( final XGridColumnModel i_reference, final XGridColumnModel i_compare )569     private void impl_assertEquality( final XGridColumnModel i_reference, final XGridColumnModel i_compare ) throws IndexOutOfBoundsException
570     {
571         assertEquals( "column model comparison: wrong column counts", i_reference.getColumnCount(), i_compare.getColumnCount() );
572         for ( int col = 0; col < i_reference.getColumnCount(); ++col )
573         {
574             final XGridColumn referenceColumn = i_reference.getColumn( col );
575             final XGridColumn compareColumn = i_compare.getColumn( col );
576             impl_assertEquality( referenceColumn, compareColumn );
577         }
578     }
579 
580     // -----------------------------------------------------------------------------------------------------------------
impl_assertEquality( final XGridColumn i_reference, final XGridColumn i_compare )581     private void impl_assertEquality( final XGridColumn i_reference, final XGridColumn i_compare )
582     {
583         final Method[] methods = XGridColumn.class.getMethods();
584         for ( int m=0; m<methods.length; ++m )
585         {
586             if ( !methods[m].getName().startsWith( "get" ) )
587                 continue;
588             try
589             {
590                 final Object referenceValue = methods[m].invoke( i_reference );
591                 final Object compareValue = methods[m].invoke( i_compare );
592                 assertEquals( "grid column comparison: column attribute '" + methods[m].getName().substring(3) + "' does not match",
593                     referenceValue, compareValue );
594             }
595             catch ( java.lang.Exception ex )
596             {
597                 fail( " could not retrieve object attributes: " + ex.toString() );
598             }
599         }
600     }
601 
602     // -----------------------------------------------------------------------------------------------------------------
impl_areSameInterface( final Object i_lhs, final Object i_rhs )603     private boolean impl_areSameInterface( final Object i_lhs, final Object i_rhs )
604     {
605         final XInterface lhs = UnoRuntime.queryInterface( XInterface.class, i_lhs );
606         final XInterface rhs = UnoRuntime.queryInterface( XInterface.class, i_rhs );
607         return UnoRuntime.areSame( lhs, rhs );
608     }
609 
610     // -----------------------------------------------------------------------------------------------------------------
611     @Before
initTestCase()612     public void initTestCase()
613     {
614         m_disposables.clear();
615     }
616 
617     // -----------------------------------------------------------------------------------------------------------------
618     @After
cleanupTestCase()619     public void cleanupTestCase()
620     {
621         impl_dispose( m_disposables.toArray() );
622     }
623 
624     // -----------------------------------------------------------------------------------------------------------------
625     @BeforeClass
setUpConnection()626     public static void setUpConnection() throws java.lang.Exception
627     {
628         System.out.println( "--------------------------------------------------------------------------------" );
629         System.out.println( "starting class: " + GridControl.class.getName() );
630         System.out.print( "connecting ... " );
631         m_connection.setUp();
632         System.out.println( "done.");
633 
634         final long seed = m_randomGenerator.nextLong();
635         m_randomGenerator.setSeed( seed );
636         System.out.println( "seeding random number generator with " + seed );
637     }
638 
639     // -----------------------------------------------------------------------------------------------------------------
640     @AfterClass
tearDownConnection()641     public static void tearDownConnection()
642         throws InterruptedException, com.sun.star.uno.Exception
643     {
644         System.out.println();
645         System.out.println( "tearing down connection" );
646         m_connection.tearDown();
647         System.out.println( "finished class: " + GridControl.class.getName() );
648         System.out.println( "--------------------------------------------------------------------------------" );
649     }
650 
651     // -----------------------------------------------------------------------------------------------------------------
createInstance( Class<T> i_interfaceClass, final String i_serviceIndentifer )652     public <T> T createInstance( Class<T> i_interfaceClass, final String i_serviceIndentifer ) throws Exception
653     {
654         return UnoRuntime.queryInterface( i_interfaceClass, createInstance( i_serviceIndentifer ) );
655     }
656 
657     // -----------------------------------------------------------------------------------------------------------------
createInstance( final String i_serviceName )658     private Object createInstance( final String i_serviceName ) throws Exception
659     {
660         Object instance = m_context.getServiceManager().createInstanceWithContext( i_serviceName, m_context );
661         assertNotNull( "could not create an instance of '" + i_serviceName + "'", instance );
662         return instance;
663     }
664     // -----------------------------------------------------------------------------------------------------------------
665     private static final class DisposeListener implements XEventListener
666     {
DisposeListener( final Object i_component )667         DisposeListener( final Object i_component )
668         {
669             m_component = UnoRuntime.queryInterface( XComponent.class, i_component );
670             assertNotNull( m_component );
671             m_component.addEventListener( this );
672         }
673 
disposing( EventObject i_event )674         public void disposing( EventObject i_event )
675         {
676             assertTrue( UnoRuntime.areSame( i_event.Source, m_component ) );
677             m_isDisposed = true;
678         }
679 
isDisposed()680         final boolean isDisposed() { return m_isDisposed; }
681 
682         private final XComponent    m_component;
683         private boolean             m_isDisposed;
684     };
685 
686     // -----------------------------------------------------------------------------------------------------------------
687     private static final class ColumnModelListener implements XContainerListener
688     {
ColumnModelListener()689         ColumnModelListener()
690         {
691         }
692 
elementInserted( ContainerEvent i_event )693         public void elementInserted( ContainerEvent i_event )
694         {
695             m_insertionEvents.add( i_event );
696         }
697 
elementRemoved( ContainerEvent i_event )698         public void elementRemoved( ContainerEvent i_event )
699         {
700             m_removalEvents.add( i_event );
701         }
702 
elementReplaced( ContainerEvent i_event )703         public void elementReplaced( ContainerEvent i_event )
704         {
705             m_replacementEvents.add( i_event );
706         }
707 
disposing( EventObject eo )708         public void disposing( EventObject eo )
709         {
710             m_isDisposed = true;
711         }
712 
assertExclusiveInsertionEvents()713         private List< ContainerEvent > assertExclusiveInsertionEvents()
714         {
715             assertFalse( m_insertionEvents.isEmpty() );
716             assertTrue( m_removalEvents.isEmpty() );
717             assertTrue( m_replacementEvents.isEmpty() );
718             return m_insertionEvents;
719         }
720 
assertExclusiveRemovalEvents()721         private List< ContainerEvent > assertExclusiveRemovalEvents()
722         {
723             assertTrue( m_insertionEvents.isEmpty() );
724             assertFalse( m_removalEvents.isEmpty() );
725             assertTrue( m_replacementEvents.isEmpty() );
726             return m_removalEvents;
727         }
728 
reset()729         private void reset()
730         {
731             m_insertionEvents = new ArrayList< ContainerEvent >();
732             m_removalEvents = new ArrayList< ContainerEvent >();
733             m_replacementEvents = new ArrayList< ContainerEvent >();
734         }
735 
getInsertionEvents()736         private List< ContainerEvent > getInsertionEvents() { return m_insertionEvents; }
getRemovalEvents()737         private List< ContainerEvent > getRemovalEvents() { return m_removalEvents; }
738 
isDisposed()739         final boolean isDisposed() { return m_isDisposed; }
740 
741         private List< ContainerEvent > m_insertionEvents = new ArrayList< ContainerEvent >();
742         private List< ContainerEvent > m_removalEvents = new ArrayList< ContainerEvent >();
743         private List< ContainerEvent > m_replacementEvents = new ArrayList< ContainerEvent >();
744         private boolean m_isDisposed = false;
745     };
746 
747     // -----------------------------------------------------------------------------------------------------------------
748     private static final OfficeConnection m_connection = new OfficeConnection();
749     private static Random m_randomGenerator = new Random();
750     private final XComponentContext m_context;
751 
752     private XPropertySet                    m_gridControlModel;
753     private XGridColumnModel                m_columnModel;
754     private XSortableMutableGridDataModel   m_dataModel;
755     private final List< Object >            m_disposables = new ArrayList< Object >();
756 }
757