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 package com.sun.star.wizards.agenda;
24 
25 import java.util.List;
26 import com.sun.star.wizards.common.HelpIds;
27 
28 import com.sun.star.awt.FocusEvent;
29 import com.sun.star.awt.Key;
30 import com.sun.star.awt.KeyEvent;
31 import com.sun.star.awt.KeyModifier;
32 import com.sun.star.awt.Selection;
33 import com.sun.star.awt.XControl;
34 import com.sun.star.awt.XFocusListener;
35 import com.sun.star.awt.XKeyListener;
36 import com.sun.star.awt.XTextComponent;
37 import com.sun.star.awt.XWindow;
38 import com.sun.star.beans.PropertyValue;
39 import com.sun.star.lang.EventObject;
40 import com.sun.star.lang.XMultiServiceFactory;
41 import com.sun.star.uno.UnoRuntime;
42 import com.sun.star.wizards.common.Helper;
43 import com.sun.star.wizards.common.Properties;
44 import com.sun.star.wizards.common.PropertyNames;
45 import com.sun.star.wizards.ui.ControlScroller;
46 import com.sun.star.wizards.ui.UnoDialog2;
47 import com.sun.star.wizards.ui.event.EventNames;
48 import com.sun.star.wizards.ui.event.MethodInvocation;
49 
50 /**
51  * @author rpiterman
52  * This class implements the UI functionality of the topics scroller control.
53  * <br/>
54  * During developement, there has been a few changes which were not *fully* done -
55  * mainly in converting the topics and time boxes from combobox and time box to normal textboxes,
56  * so in the code they might be referenced as combobox or timebox. This should be
57  * rather understood as topicstextbox and timetextbox.
58  * <br/>
59  * <br/>
60  * Important behaiviour of this control is that there is always a
61  * blank row at the end, in which the user can enter data.<br/>
62  * Once the row is not blank (thus, the user entered data...),
63  * a new blank row is added.<br/>
64  * Once the user removes the last *unempty* row, by deleteing its data, it becomes
65  * the *last empty row* and the one after is being automatically removed.<br/>
66  * <br/>
67  * The contorl shows 5 rows at a time.<br/>
68  * If, for example, only 2 rows exist (from which the 2ed one is empty...)
69  * then the other three rows, which do not exist in the data model, are disabled.
70  * <br/>
71  * The following other functionality is implemented:
72  * <br/>
73  * 0. synchroniting data between controls, data model and live preview.
74  * 1. Tab scrolling.<br/>
75  * 2. Keyboard scrolling.<br/>
76  * 3. Removing rows and adding new rows.<br/>
77  * 4. Moving rows up and down. <br/>
78  * <br/>
79  * This control relays on the ControlScroller control which uses the following
80  * Data model:<br/>
81  * 1. It uses a vector, whos members are arrays of PropertyValue.<br/>
82  * 2. Each array represents a row.<br/>
83  * (Note: the Name and Value memebrs of the PropertyValue object are being used...)
84  * 3. Each property Value represents a value for a single control with the following rules:<br/>
85  * 3. a. the Value of the property is used for as value of the controls (usually text).<br/>
86  * 3. b. the Name of the property is used to map values to UI controls in the following manner:<br/>
87  * 3. b. 1. only the Name of the first X Rows is regarded, where X is the number of visible rows
88  * (in the agenda wizard this would be 5, since 5 topic rows are visible on the dialog).<br/>
89  * 3. b. 2. The Names of the first X (or 5...) rows are the names of the UI Controls to
90  * hold values. When the control scroller scrolls, it looks at the first 5 rows and uses
91  * the names specified there to map the current values to the specified controls.
92  * <br/>
93  * This data model makes the following limitations on the implementation:
94  * When moving rows, only the values should be moved. The Rows objects, which contain
95  * also the Names of the controls should not be switched. <br/>
96  * also when deleting or inserting rows, attention should be paid that no rows should be removed
97  * or inserted. Instead, only the Values should rotate.
98  * <br/>
99  * <br/>
100  * To save the topics in the registry a ConfigSet of objects of type CGTopic is
101  * being used.
102  * This one is not synchronized "live", since it is unnecessary... instead, it is
103  * synchronized on call, before the settings should be saved.
104  */
105 public class TopicsControl extends ControlScroller implements XFocusListener
106 {
107 
108     /**
109      * The name prefix of the number (label) controls
110      */
111     public static final String LABEL = "lblTopicCnt_";
112     /**
113      * The name prefix of the topic (text) controls
114      */
115     public static final String TOPIC = "txtTopicTopic_";
116     /**
117      * The name prefix of the responsible (text) controls
118      */
119     public static final String RESP = "cbTopicResp_";
120     /**
121      * The name prefix of the time (text) controls
122      */
123     public static final String TIME = "txtTopicTime_";
124     Object lastFocusControl;
125     int lastFocusRow;
126     /**
127      * the last
128      * topic text box.
129      * When pressing tab on this one a scroll down *may* be performed.
130      */
131     private Object firstTopic;
132     /**
133      * the first time box.
134      * When pressing shift-tab on this control, a scroll up *may* be performed.
135      */
136     private Object lastTime;
137     /**
138      * is used when constructing to track the tab index
139      * of the created control rows.
140      */
141     private int tabIndex = 520;
142 
143     /**
144      * create a new TopicControl. Since this is used specifically for the
145      * agenda dialog, I use step 5, and constant location - and need no paramter...
146      * @param dialog the parent dialog
147      * @param xmsf service factory
148      * @param agenda the Agenda configuration data (contains the current topics data).
149      */
TopicsControl(AgendaWizardDialog dialog, XMultiServiceFactory xmsf, CGAgenda agenda)150     public TopicsControl(AgendaWizardDialog dialog, XMultiServiceFactory xmsf, CGAgenda agenda)
151     {
152         super(dialog, xmsf, 5, 92, 38, 212, 5, 18, AgendaWizardDialogConst.LAST_HID);
153         initializeScrollFields(agenda);
154         initialize(agenda.cp_Topics.getSize() + 1);
155 
156         // set some focus listeners for TAB scroll down and up...
157         try
158         {
159 
160             // prepare scroll down on tab press...
161             Object lastTime = ((ControlRow) ControlGroupVector.get(nblockincrement - 1)).timebox;
162 
163             MethodInvocation mi = new MethodInvocation("lastControlKeyPressed", this, KeyEvent.class);
164             dialog.getGuiEventListener().add(TIME + (nblockincrement - 1), EventNames.EVENT_KEY_PRESSED, mi);
165 
166             addKeyListener(lastTime, (XKeyListener) dialog.getGuiEventListener());
167 
168             //prepare scroll up on tab press...
169             firstTopic = ((ControlRow) ControlGroupVector.get(0)).textbox;
170 
171             mi = new MethodInvocation("firstControlKeyPressed", this, KeyEvent.class);
172             dialog.getGuiEventListener().add(TOPIC + 0, EventNames.EVENT_KEY_PRESSED, mi);
173 
174             addKeyListener(firstTopic, (XKeyListener) dialog.getGuiEventListener());
175 
176         }
177         catch (NoSuchMethodException ex)
178         {
179             ex.printStackTrace();
180         }
181 
182     }
183 
184     /**
185      * Is used to add a keylistener to different controls...
186      */
addKeyListener(Object control, XKeyListener listener)187     static void addKeyListener(Object control, XKeyListener listener)
188     {
189         XWindow xlastControl = UnoRuntime.queryInterface(XWindow.class,
190                 control);
191         xlastControl.addKeyListener(listener);
192     }
193 
194     /**
195      * Is used to add a focuslistener to different controls...
196      */
addFocusListener(Object control, XFocusListener listener)197     static void addFocusListener(Object control, XFocusListener listener)
198     {
199         XWindow xlastControl = UnoRuntime.queryInterface(XWindow.class,
200                 control);
201         xlastControl.addFocusListener(listener);
202     }
203 
204     /**
205      * Implementation of the parent class...
206      */
initializeScrollFields()207     protected void initializeScrollFields()
208     {
209     }
210 
211     /**
212      * initializes the data of the control.
213      * @param agenda
214      */
initializeScrollFields(CGAgenda agenda)215     protected void initializeScrollFields(CGAgenda agenda)
216     {
217         // create a row for each topic with the given values....
218         for (int i = 0; i < agenda.cp_Topics.getSize(); i++)
219         {
220             PropertyValue[] row = newRow(i);
221             ((CGTopic) agenda.cp_Topics.getElementAt(i)).setDataToRow(row);
222             // a parent class method
223             registerControlGroup(row, i);
224             this.updateDocumentRow(i);
225         }
226         // inserts a blank row at the end...
227         insertRowAtEnd();
228     }
229 
230     /**
231      * Insert a blank (empty) row
232      * as last row of the control.
233      * The control has always a blank row at the
234      * end, which enables the user to enter data...
235      */
insertRowAtEnd()236     protected void insertRowAtEnd()
237     {
238         int l = scrollfields.size();
239         registerControlGroup(newRow(l), l);
240         setTotalFieldCount(l + 1);
241 
242         // if the new row is visible, it must have been disabled
243         // so it should be now enabled...
244         if (l - nscrollvalue < nblockincrement)
245         {
246             ((ControlRow) ControlGroupVector.get(l - nscrollvalue)).setEnabled(true);
247         }
248     }
249 
250     /**
251      * The Topics Set in the CGAgenda object is synchronized to
252      * the current content of the topics.
253      * @param agenda
254      */
saveTopics(CGAgenda agenda)255     void saveTopics(CGAgenda agenda)
256     {
257         agenda.cp_Topics.clear();
258         for (int i = 0; i < scrollfields.size() - 1; i++)
259         {
260             agenda.cp_Topics.add(i,
261                     new CGTopic(scrollfields.get(i)));
262         }
263     }
264 
265     /**
266      * overrides the parent class method to also enable the
267      * row whenever data is written to it.
268      * @param guiRow
269      */
fillupControls(int guiRow)270     protected void fillupControls(int guiRow)
271     {
272         super.fillupControls(guiRow);
273         ((ControlRow) ControlGroupVector.get(guiRow)).setEnabled(true);
274     }
275 
276     /**
277      * remove the last row
278      */
removeLastRow()279     protected void removeLastRow()
280     {
281         int l = scrollfields.size();
282 
283         // if we should scroll up...
284         if ((l - nscrollvalue >= 1) && (l - nscrollvalue <= nblockincrement) && nscrollvalue > 0)
285         {
286             while ((l - nscrollvalue >= 1) && (l - nscrollvalue <= nblockincrement) && nscrollvalue > 0)
287             {
288                 setScrollValue(nscrollvalue - 1);
289             }
290         }
291         // if we should disable a row...
292         else if (nscrollvalue == 0 && l - 1 < nblockincrement)
293         {
294             ControlRow cr = (ControlRow) ControlGroupVector.get(l - 1);
295             cr.setEnabled(false);
296         }
297 
298         unregisterControlGroup(l - 1);
299         setTotalFieldCount(l - 1);
300     }
301 
302     /**
303      * in order to use the "move up", "downPropertyNames.SPACEinsert" and "remove" buttons,
304      * we track the last control the gained focus, in order to know which
305      * row should be handled.
306      * @param fe
307      */
focusGained(FocusEvent fe)308     public void focusGained(FocusEvent fe)
309     {
310         XControl xc = UnoRuntime.queryInterface(XControl.class, fe.Source);
311         focusGained(xc);
312     }
313 
314     /**
315      * Sometimes I set the focus programatically to a control
316      * (for example when moving a row up or down, the focus should move
317      * with it).
318      * In such cases, no VCL event is being triggered so it must
319      * be called programtically.
320      * This is done by this method.
321      * @param control
322      */
focusGained(XControl control)323     private void focusGained(XControl control)
324     {
325         try
326         {
327             //calculate in which row we are...
328             String name = (String) Helper.getUnoPropertyValue(UnoDialog2.getModel(control), PropertyNames.PROPERTY_NAME);
329             int i = name.indexOf("_");
330             String num = name.substring(i + 1);
331             lastFocusRow = Integer.valueOf(num).intValue() + nscrollvalue;
332             lastFocusControl = control;
333             // enable/disable the buttons...
334             enableButtons();
335         }
336         catch (Exception ex)
337         {
338             ex.printStackTrace();
339         }
340     }
341 
342     /**
343      * enable or disable the buttons according to the
344      * current row we are in.
345      */
enableButtons()346     private void enableButtons()
347     {
348         UnoDialog2.setEnabled(getAD().btnInsert, (lastFocusRow < scrollfields.size() - 1 ? Boolean.TRUE : Boolean.FALSE));
349         UnoDialog2.setEnabled(getAD().btnRemove, (lastFocusRow < scrollfields.size() - 1 ? Boolean.TRUE : Boolean.FALSE));
350         UnoDialog2.setEnabled(getAD().btnUp, (lastFocusRow > 0 ? Boolean.TRUE : Boolean.FALSE));
351         UnoDialog2.setEnabled(getAD().btnDown, (lastFocusRow < scrollfields.size() - 1 ? Boolean.TRUE : Boolean.FALSE));
352     }
353 
354     /**
355      * compolsary implementation of FocusListener.
356      * @param fe
357      */
358     public void focusLost(FocusEvent fe)
359     {
360     }
361 
362     /**
363      * compolsary implementation of FocusListener.
364      * @param o
365      */
366     public void disposing(EventObject o)
367     {
368     }
369 
370     /**
371      * Convenience method. Is used to get a reference of
372      * the template controller (live preview in background).
373      * @return the parent dialog, casted to AgendaWizardDialog.
374      */
375     private AgendaWizardDialog getAD()
376     {
377         return (AgendaWizardDialog) this.CurUnoDialog;
378     }
379 
380     /**
381      * move the current row up
382      */
383     public void rowUp()
384     {
385         rowUp(lastFocusRow - nscrollvalue, lastFocusControl);
386     }
387 
388     /**
389      * move the current row down.
390      */
391     public void rowDown()
392     {
393         rowDown(lastFocusRow - nscrollvalue, lastFocusControl);
394     }
395 
396     private void lockDoc()
397     {
398         //((AgendaWizardDialogImpl)CurUnoDialog).agendaTemplate.xTextDocument.lockControllers();
399     }
400 
401     private void unlockDoc()
402     {
403         //((AgendaWizardDialogImpl)CurUnoDialog).agendaTemplate.xTextDocument.unlockControllers();
404     }
405 
406     /**
407      * Removes the current row.
408      * See general class documentation explanation about the
409      * data model used and the limitations which explain the implementation here.
410      */
411     public void removeRow()
412     {
413         lockDoc();
414         for (int i = lastFocusRow; i < scrollfields.size() - 1; i++)
415         {
416             PropertyValue[] pv1 = (PropertyValue[]) scrollfields.get(i);
417             PropertyValue[] pv2 = (PropertyValue[]) scrollfields.get(i + 1);
418             pv1[1].Value = pv2[1].Value;
419             pv1[2].Value = pv2[2].Value;
420             pv1[3].Value = pv2[3].Value;
421             updateDocumentRow(i);
422             if (i - nscrollvalue < nblockincrement)
423             {
424                 fillupControls(i - nscrollvalue);
425             }
426         }
427         removeLastRow();
428         // update the live preview background document
429         reduceDocumentToTopics();
430 
431         // the focus should return to the edit control
432         focus(lastFocusControl);
433         unlockDoc();
434     }
435 
436     /**
437      * Inserts a row before the current row.
438      * See general class documentation explanation about the
439      * data model used and the limitations which explain the implementation here.
440      */
441     public void insertRow()
442     {
443         lockDoc();
444         insertRowAtEnd();
445         for (int i = scrollfields.size() - 2; i > lastFocusRow; i--)
446         {
447             PropertyValue[] pv1 = (PropertyValue[]) scrollfields.get(i);
448             PropertyValue[] pv2 = (PropertyValue[]) scrollfields.get(i - 1);
449             pv1[1].Value = pv2[1].Value;
450             pv1[2].Value = pv2[2].Value;
451             pv1[3].Value = pv2[3].Value;
452             updateDocumentRow(i);
453             if (i - nscrollvalue < nblockincrement)
454             {
455                 fillupControls(i - nscrollvalue);
456             }
457         }
458 
459         // after rotating all the properties from this row on,
460         // we clear the row, so it is practically a new one...
461         PropertyValue[] pv1 = (PropertyValue[]) scrollfields.get(lastFocusRow);
462         pv1[1].Value = PropertyNames.EMPTY_STRING;
463         pv1[2].Value = PropertyNames.EMPTY_STRING;
464         pv1[3].Value = PropertyNames.EMPTY_STRING;
465 
466         // update the preview document.
467         updateDocumentRow(lastFocusRow);
468 
469         fillupControls(lastFocusRow - nscrollvalue);
470 
471         focus(lastFocusControl);
472         unlockDoc();
473     }
474 
475     /**
476      * create a new row with the given index.
477      * The index is important because it is used in the
478      * Name member of the PropertyValue objects.
479      * To know why see general class documentation above (data model explanation).
480      * @param i the index of the new row
481      * @return
482      */
483     private PropertyValue[] newRow(int i)
484     {
485         PropertyValue[] pv = new PropertyValue[4];
486         pv[0] = Properties.createProperty(LABEL + i, PropertyNames.EMPTY_STRING + (i + 1) + ".");
487         pv[1] = Properties.createProperty(TOPIC + i, PropertyNames.EMPTY_STRING);
488         pv[2] = Properties.createProperty(RESP + i, PropertyNames.EMPTY_STRING);
489         pv[3] = Properties.createProperty(TIME + i, PropertyNames.EMPTY_STRING);
490         return pv;
491     }
492 
493     /**
494      * Implementation of ControlScroller
495      * This is a UI method which inserts a new row to the control.
496      * It uses the child-class ControlRow. (see below).
497      * @param _index
498      * @param npos
499      * @see ControlRow
500      */
501     protected void insertControlGroup(int _index, int npos)
502     {
503         ControlRow oControlRow = new ControlRow((AgendaWizardDialog) CurUnoDialog, iCompPosX, npos, _index, tabIndex);
504         ControlGroupVector.addElement(oControlRow);
505         tabIndex += 4;
506     }
507 
508     /**
509      * Implementation of ControlScroller
510      * This is a UI method which makes a row visibele.
511      * As far as I know it is never called.
512      * @param _index
513      * @param _bIsVisible
514      * @see ControlRow
515      */
516     protected void setControlGroupVisible(int _index, boolean _bIsVisible)
517     {
518         ((ControlRow) ControlGroupVector.get(_index)).setVisible(_bIsVisible);
519 
520     }
521 
522     /**
523      * Checks if a row is empty.
524      * This is used when the last row is changed.
525      * If it is empty, the next row (which is always blank) is removed.
526      * If it is not empty, a next row must exist.
527      * @param row the index number of the row to check.
528      * @return true if empty. false if not.
529      */
530     protected boolean isRowEmpty(int row)
531     {
532         PropertyValue[] data = getTopicData(row);
533 
534         // now - is this row empty?
535         return data[1].Value.equals(PropertyNames.EMPTY_STRING) &&
536                 data[2].Value.equals(PropertyNames.EMPTY_STRING) &&
537                 data[3].Value.equals(PropertyNames.EMPTY_STRING);
538 
539     }
540     /**
541      * is used for data tracking.
542      */
543     private Object[] oldData;
544 
545     /**
546      * update the preview document and
547      * remove/insert rows if needed.
548      * @param guiRow
549      * @param column
550      */
551     synchronized void fieldChanged(int guiRow, int column)
552     {
553         synchronized(this)
554         {
555 
556             try
557             {
558                 // First, I update the document
559                 PropertyValue[] data = getTopicData(guiRow + nscrollvalue);
560 
561                 if (data == null)
562                 {
563                     return;
564                 }
565                 boolean equal = true;
566 
567                 if (oldData != null)
568                 {
569                     for (int i = 0; i < data.length && equal; i++)
570                     {
571                         equal = (equal & data[i].Value.equals(oldData[i]));
572                     }
573                     if (equal)
574                     {
575                         return;
576                     }
577                 }
578                 else
579                 {
580                     oldData = new Object[4];
581                 }
582                 for (int i = 0; i < data.length; i++)
583                 {
584                     oldData[i] = data[i].Value;
585                 }
586                 updateDocumentCell(guiRow + nscrollvalue, column, data);
587 
588                 if (isRowEmpty(guiRow + nscrollvalue))
589                 {
590                     /* if this is the row before the last one
591                      * (the last row is always empty)
592                      * delete the last row...
593                      */
594                     if (guiRow + nscrollvalue == scrollfields.size() - 2)
595                     {
596                         removeLastRow();
597 
598                         /*
599                          * now consequentially check the last two rows,
600                          * and remove the last one if they are both empty.
601                          * (actually I check always the "before last" row,
602                          * because the last one is always empty...
603                          */
604                         while (scrollfields.size() > 1 && isRowEmpty(scrollfields.size() - 2))
605                         {
606                             removeLastRow();
607                         }
608                         ControlRow cr = (ControlRow) ControlGroupVector.get(scrollfields.size() - nscrollvalue - 1);
609 
610                         // if a remove was performed, set focus to the last row with some data in it...
611                         focus(getControl(cr, column));
612 
613                         // update the preview document.
614                         reduceDocumentToTopics();
615                     }
616 
617                 }
618                 else
619                 { // row contains data
620                     // is this the last row?
621                     if ((guiRow + nscrollvalue + 1) == scrollfields.size())
622                     {
623                         insertRowAtEnd();
624                     }
625                 }
626             }
627             catch (Exception e)
628             {
629                 e.printStackTrace();
630             }
631 
632         }
633     }
634 
635     /**
636      * return the corresponding row data for the given index.
637      * @param topic index of the topic to get.
638      * @return a PropertyValue array with the data for the given topic.
639      */
640     public PropertyValue[] getTopicData(int topic)
641     {
642         if (topic < scrollfields.size())
643         {
644             return (PropertyValue[]) scrollfields.get(topic);
645         }
646         else
647         {
648             return null;
649         }
650     }
651 
652     /**
653      * If the user presses tab on the last control, and
654      * there *are* more rows in the model, scroll down.
655      * @param event
656      */
657     public void lastControlKeyPressed(KeyEvent event)
658     {
659         // if tab without shift was pressed...
660         if ((event.KeyCode == Key.TAB) && (event.Modifiers == 0))
661         // if there is another row...
662         {
663             if ((nblockincrement + nscrollvalue) < scrollfields.size())
664             {
665                 setScrollValue(nscrollvalue + 1);
666                 //focus(firstTopic);
667                 focus(getControl((ControlRow) ControlGroupVector.get(4), 1));
668 
669             }
670         }
671     }
672 
673     /**
674      * If the user presses shift-tab on the first control, and
675      * there *are* more rows in the model, scroll up.
676      * @param event
677      */
678     public void firstControlKeyPressed(KeyEvent event)
679     {
680         // if tab with shift was pressed...
681         if ((event.KeyCode == Key.TAB) && (event.Modifiers == KeyModifier.SHIFT))
682         {
683             if (nscrollvalue > 0)
684             {
685                 setScrollValue(nscrollvalue - 1);
686                 focus(lastTime);
687             }
688         }
689     }
690 
691     /**
692      * sets focus to the given control.
693      * @param textControl
694      */
695     private void focus(Object textControl)
696     {
697         UnoRuntime.queryInterface(XWindow.class, textControl).setFocus();
698         XTextComponent xTextComponent = UnoRuntime.queryInterface(XTextComponent.class, textControl);
699         String text = xTextComponent.getText();
700         xTextComponent.setSelection(new Selection(0, text.length()));
701         XControl xc = UnoRuntime.queryInterface(XControl.class, textControl);
702         focusGained(xc);
703     }
704 
705     /**
706      * moves the given row one row down.
707      * @param guiRow the gui index of the row to move.
708      * @param control the control to gain focus after moving.
709      */
710     synchronized void rowDown(int guiRow, Object control)
711     {
712         // only perform if this is not the last row.
713         int actuallRow = guiRow + nscrollvalue;
714         if (actuallRow + 1 < scrollfields.size())
715         {
716             // get the current selection
717             Selection selection = getSelection(control);
718 
719             // the last row should scroll...
720             boolean scroll = guiRow == (nblockincrement - 1);
721             if (scroll)
722             {
723                 setScrollValue(nscrollvalue + 1);
724             }
725             int scroll1 = nscrollvalue;
726 
727             switchRows(guiRow, guiRow + (scroll ? -1 : 1));
728 
729             if (nscrollvalue != scroll1)
730             {
731                 guiRow += (nscrollvalue - scroll1);
732             }
733             setSelection(guiRow + (scroll ? 0 : 1), control, selection);
734         }
735     }
736 
737     synchronized void rowUp(int guiRow, Object control)
738     {
739         // only perform if this is not the first row
740         int actuallRow = guiRow + nscrollvalue;
741         if (actuallRow > 0)
742         {
743             // get the current selection
744             Selection selection = getSelection(control);
745 
746             // the last row should scroll...
747             boolean scroll = (guiRow == 0);
748             if (scroll)
749             {
750                 setScrollValue(nscrollvalue - 1);
751             }
752             switchRows(guiRow, guiRow + (scroll ? 1 : -1));
753 
754             setSelection(guiRow - (scroll ? 0 : 1), control, selection);
755         }
756     }
757 
758     /**
759      * moves the cursor up.
760      * @param guiRow
761      * @param control
762      */
763     synchronized void cursorUp(int guiRow, Object control)
764     {
765         // is this the last full row ?
766         int actuallRow = guiRow + nscrollvalue;
767         //if this is the first row
768         if (actuallRow == 0)
769         {
770             return;
771         // the first row should scroll...
772         }
773         boolean scroll = (guiRow == 0);
774         ControlRow upperRow;
775         if (scroll)
776         {
777             setScrollValue(nscrollvalue - 1);
778             upperRow = (ControlRow) ControlGroupVector.get(guiRow);
779         }
780         else
781         {
782             upperRow = (ControlRow) ControlGroupVector.get(guiRow - 1);
783         }
784         focus(getControl(upperRow, control));
785 
786     }
787 
788     /**
789      * moves the cursor down
790      * @param guiRow
791      * @param control
792      */
793     synchronized void cursorDown(int guiRow, Object control)
794     {
795         // is this the last full row ?
796         int actuallRow = guiRow + nscrollvalue;
797         //if this is the last row, exit
798         if (actuallRow == scrollfields.size() - 1)
799         {
800             return;
801         // the first row should scroll...
802         }
803         boolean scroll = (guiRow == nblockincrement - 1);
804         ControlRow lowerRow;
805         if (scroll)
806         {
807             setScrollValue(nscrollvalue + 1);
808             lowerRow = (ControlRow) ControlGroupVector.get(guiRow);
809         }
810         // if we scrolled we are done...
811         //otherwise...
812         else
813         {
814             lowerRow = (ControlRow) ControlGroupVector.get(guiRow + 1);
815         }
816         focus(getControl(lowerRow, control));
817     }
818 
819     /**
820      * changes the values of the given rows with eachother
821      * @param row1 one can figure out what this parameter is...
822      * @param row2 one can figure out what this parameter is...
823      */
824     private void switchRows(int row1, int row2)
825     {
826         PropertyValue[] o1 = (PropertyValue[]) scrollfields.get(row1 + nscrollvalue);
827         PropertyValue[] o2 = (PropertyValue[]) scrollfields.get(row2 + nscrollvalue);
828 
829         Object temp = null;
830         for (int i = 1; i < o1.length; i++)
831         {
832             temp = o1[i].Value;
833             o1[i].Value = o2[i].Value;
834             o2[i].Value = temp;
835         }
836 
837         fillupControls(row1);
838         fillupControls(row2);
839 
840         updateDocumentRow(row1 + nscrollvalue, o1);
841         updateDocumentRow(row2 + nscrollvalue, o2);
842 
843         /*
844          * if we changed the last row, add another one...
845          */
846         if ((row1 + nscrollvalue + 1 == scrollfields.size()) ||
847                 (row2 + nscrollvalue + 1 == scrollfields.size()))
848         {
849             insertRowAtEnd();
850         /*
851          * if we did not change the last row but
852          * we did change the one before - check if we
853          * have two empty rows at the end.
854          * If so, delete the last one...
855          */
856         }
857         else if ((row1 + nscrollvalue) + (row2 + nscrollvalue) == (scrollfields.size() * 2 - 5))
858         {
859             if (isRowEmpty(scrollfields.size() - 2) && isRowEmpty(scrollfields.size() - 1))
860             {
861                 removeLastRow();
862                 reduceDocumentToTopics();
863             }
864         }
865     }
866 
867     /**
868      * returns the current Selection of a text field
869      * @param control a text field from which the Selection object
870      * should be gotten.
871      * @return the selection object.
872      */
873     private Selection getSelection(Object control)
874     {
875         return UnoRuntime.queryInterface(XTextComponent.class, control).getSelection();
876     }
877 
878     /**
879      * sets a text selection to a given control.
880      * This is used when one moves a row up or down.
881      * After moving row X to X+/-1, the selection (or cursor position) of the
882      * last focused control should be restored.
883      * The control's row is the given guiRow.
884      * The control's column is detecte4d according to the given event.
885      * This method is called as subsequent to different events,
886      * thus it is comfortable to use the event here to detect the column,
887      * rather than in the different event methods.
888      * @param guiRow the row of the control to set the selection to.
889      * @param eventSource helps to detect the control's column to set the selection to.
890      * @param s the selection object to set.
891      */
892     private void setSelection(int guiRow, Object eventSource, Selection s)
893     {
894         ControlRow cr = (ControlRow) ControlGroupVector.get(guiRow);
895         Object control = getControl(cr, eventSource);
896         UnoRuntime.queryInterface(XWindow.class, control).setFocus();
897         UnoRuntime.queryInterface(XTextComponent.class, control).setSelection(s);
898     }
899 
900     /**
901      * returns a control out of the given row, according to a column number.
902      * @param cr control row object.
903      * @param column the column number.
904      * @return the control...
905      */
906     private Object getControl(ControlRow cr, int column)
907     {
908         switch (column)
909         {
910             case 0:
911                 return cr.label;
912             case 1:
913                 return cr.textbox;
914             case 2:
915                 return cr.combobox;
916             case 3:
917                 return cr.timebox;
918             default:
919                 throw new IllegalArgumentException("No such column");
920         }
921     }
922 
923     /**
924      * returns a control out of the given row, which is
925      * in the same column as the given control.
926      * @param cr control row object
927      * @param control a control indicating a column.
928      * @return
929      */
930     private Object getControl(ControlRow cr, Object control)
931     {
932         int column = getColumn(control);
933         return getControl(cr, column);
934     }
935 
936     /**
937      * returns the column number of the given control.
938      * @param control
939      * @return
940      */
941     private int getColumn(Object control)
942     {
943         String name = (String) Helper.getUnoPropertyValue(UnoDialog2.getModel(control), PropertyNames.PROPERTY_NAME);
944         if (name.startsWith(TOPIC))
945         {
946             return 1;
947         }
948         if (name.startsWith(RESP))
949         {
950             return 2;
951         }
952         if (name.startsWith(TIME))
953         {
954             return 3;
955         }
956         if (name.startsWith(LABEL))
957         {
958             return 0;
959         }
960         return -1;
961     }
962 
963     /**
964      * updates the given row in the preview document.
965      * @param row
966      */
967     private void updateDocumentRow(int row)
968     {
969         updateDocumentRow(row, (PropertyValue[]) scrollfields.get(row));
970     }
971 
972     /**
973      * update the given row in the preview document with the given data.
974      * @param row
975      * @param data
976      */
977     private void updateDocumentRow(int row, PropertyValue[] data)
978     {
979         try
980         {
981             ((AgendaWizardDialogImpl) CurUnoDialog).agendaTemplate.topics.write(row, data);
982         }
983         catch (Exception ex)
984         {
985             ex.printStackTrace();
986         }
987     }
988 
989     /**
990      * updates a single cell in the preview document.
991      * Is called when a single value is changed, since we really
992      * don't have to update the whole row for one small changhe...
993      * @param row the data row to update (topic number).
994      * @param column the column to update (a gui column, not a document column).
995      * @param data the data of the entire row.
996      */
997     private void updateDocumentCell(int row, int column, PropertyValue[] data)
998     {
999         try
1000         {
1001             ((AgendaWizardDialogImpl) CurUnoDialog).agendaTemplate.topics.writeCell(row, column, data);
1002         }
1003         catch (Exception ex)
1004         {
1005             ex.printStackTrace();
1006         }
1007     }
1008 
1009     /**
1010      * when removeing rows, this method updates
1011      * the preview document to show the number of rows
1012      * according to the data model.
1013      */
1014     private void reduceDocumentToTopics()
1015     {
1016         try
1017         {
1018             ((AgendaWizardDialogImpl) CurUnoDialog).agendaTemplate.topics.reduceDocumentTo(scrollfields.size() - 1);
1019         }
1020         catch (Exception ex)
1021         {
1022             ex.printStackTrace();
1023         }
1024     }
1025 
1026     /**
1027      * needed to make this data poblic.
1028      * @return the List containing the topics data.
1029      */
1030     public List getTopicsData()
1031     {
1032         return scrollfields;
1033     }
1034     /**
1035      * A static member used for the child-class ControlRow (GUI Constant)
1036      */
1037     private static Integer I_12 = 12;
1038     /**
1039      * A static member used for the child-class ControlRow (GUI Constant)
1040      */
1041     private static Integer I_8 = 8;
1042     /**
1043      * A static member used for the child-class ControlRow (GUI Constant)
1044      */
1045     private static final String[] LABEL_PROPS = new String[]
1046     {
1047         PropertyNames.PROPERTY_HEIGHT, PropertyNames.PROPERTY_LABEL, PropertyNames.PROPERTY_POSITION_X, PropertyNames.PROPERTY_POSITION_Y, PropertyNames.PROPERTY_STEP, PropertyNames.PROPERTY_TABINDEX, PropertyNames.PROPERTY_WIDTH
1048     };
1049     /**
1050      * A static member used for the child-class ControlRow (GUI Constant)
1051      */
1052     private static final String[] TEXT_PROPS = new String[]
1053     {
1054         PropertyNames.PROPERTY_HEIGHT, PropertyNames.PROPERTY_HELPURL, PropertyNames.PROPERTY_POSITION_X, PropertyNames.PROPERTY_POSITION_Y, PropertyNames.PROPERTY_STEP, PropertyNames.PROPERTY_TABINDEX, PropertyNames.PROPERTY_WIDTH
1055     };
1056 
1057     /**
1058      *
1059      * @author rp143992
1060      * A class represting a single GUI row.
1061      * Note that the instance methods of this class
1062      * are being called and handle controls of
1063      * a single row.
1064      */
1065     public class ControlRow implements XKeyListener
1066     {
1067 
1068         /**
1069          * the number (label) control
1070          */
1071         Object label;
1072         /**
1073          * the topic (text) control
1074          */
1075         Object textbox;
1076         /**
1077          * the responsible (text, yes, text) control
1078          */
1079         Object combobox;
1080         /**
1081          * the time (text, yes, text) control
1082          */
1083         Object timebox;
1084         /**
1085          * the row offset of this instance (0 = first gui row)
1086          */
1087         int offset;
1088 
1089         /**
1090          * called through an event listener when the
1091          * topic text is changed by the user.
1092          * updates the data model and the preview document.
1093          */
1094         public void topicTextChanged()
1095         {
1096             try
1097             {
1098                 // update the data model
1099                 fieldInfo(offset, 1);
1100                 // update the preview document
1101                 fieldChanged(offset, 1);
1102             }
1103             catch (Exception ex)
1104             {
1105                 ex.printStackTrace();
1106             }
1107         }
1108 
1109         /**
1110          * called through an event listener when the
1111          * responsible text is changed by the user.
1112          * updates the data model and the preview document.
1113          */
1114         public void responsibleTextChanged()
1115         {
1116             try
1117             {
1118                 // update the data model
1119                 fieldInfo(offset, 2);
1120                 // update the preview document
1121                 fieldChanged(offset, 2);
1122             }
1123             catch (Exception ex)
1124             {
1125                 ex.printStackTrace();
1126             }
1127         }
1128 
1129         /**
1130          * called through an event listener when the
1131          * time text is changed by the user.
1132          * updates the data model and the preview document.
1133          */
1134         public void timeTextChanged()
1135         {
1136             try
1137             {
1138                 // update the data model
1139                 fieldInfo(offset, 3);
1140                 // update the preview document
1141                 fieldChanged(offset, 3);
1142             }
1143             catch (Exception ex)
1144             {
1145                 ex.printStackTrace();
1146             }
1147         }
1148 
1149         /**
1150          * constructor. Create the row in the given dialog given cordinates,
1151          * with the given offset (row number) and tabindex.
1152          * Note that since I use this specifically for the agenda wizard,
1153          * the step and all control coordinates inside the
1154          * row are constant (5).
1155          * @param dialog the agenda dialog
1156          * @param x x coordinates
1157          * @param y y coordinates
1158          * @param i the gui row index
1159          * @param tabindex first tab index for this row.
1160          */
1161         public ControlRow(AgendaWizardDialog dialog, int x, int y, int i, int tabindex)
1162         {
1163 
1164             offset = i;
1165 
1166             Integer y_ = new Integer(y);
1167 
1168             label = dialog.insertLabel(LABEL + i,
1169                     LABEL_PROPS,
1170                     new Object[]
1171                     {
1172                         I_8, PropertyNames.EMPTY_STRING + (i + 1) + ".", new Integer(x + 4), new Integer(y + 2), IStep, new Short((short) tabindex), 10
1173                     });
1174 
1175             textbox = dialog.insertTextField(TOPIC + i, "topicTextChanged", this,
1176                     TEXT_PROPS,
1177                     new Object[]
1178                     {
1179                         I_12, HelpIds.getHelpIdString(curHelpIndex + i * 3 + 1), new Integer(x + 15), y_, IStep, new Short((short) (tabindex + 1)), 84
1180                     });
1181 
1182             combobox = dialog.insertTextField(RESP + i, "responsibleTextChanged", this,
1183                     TEXT_PROPS,
1184                     new Object[]
1185                     {
1186                         I_12, HelpIds.getHelpIdString(curHelpIndex + i * 3 + 2), new Integer(x + 103), y_, IStep, new Short((short) (tabindex + 2)), 68
1187                     });
1188 
1189             timebox = dialog.insertTextField(TIME + i, "timeTextChanged", this,
1190                     TEXT_PROPS,
1191                     new Object[]
1192                     {
1193                         I_12, HelpIds.getHelpIdString(curHelpIndex + i * 3 + 3), new Integer(x + 175), y_, IStep, new Short((short) (tabindex + 3)), 20
1194                     });
1195 
1196             setEnabled(false);
1197             addKeyListener(textbox, this);
1198             addKeyListener(combobox, this);
1199             addKeyListener(timebox, this);
1200 
1201             addFocusListener(textbox, TopicsControl.this);
1202             addFocusListener(combobox, TopicsControl.this);
1203             addFocusListener(timebox, TopicsControl.this);
1204 
1205         }
1206 
1207         /**
1208          * not implemented.
1209          * @param visible
1210          */
1211         public void setVisible(boolean visible)
1212         {
1213             //  Helper.setUnoPropertyValue(UnoDialog2.getModel(button),"Visible", visible ? Boolean.TRUE : Boolean.FASLE);
1214         }
1215 
1216         /**
1217          * enables/disables the row.
1218          * @param enabled true for enable, false for disable.
1219          */
1220         public void setEnabled(boolean enabled)
1221         {
1222             Boolean b = enabled ? Boolean.TRUE : Boolean.FALSE;
1223             UnoDialog2.setEnabled(label, b);
1224             UnoDialog2.setEnabled(textbox, b);
1225             UnoDialog2.setEnabled(combobox, b);
1226             UnoDialog2.setEnabled(timebox, b);
1227         }
1228 
1229         /**
1230          * Impelementation of XKeyListener.
1231          * Optionally performs the one of the following:
1232          * cursor up, or down, row up or down
1233          */
1234         public void keyPressed(KeyEvent event)
1235         {
1236             if (isMoveDown(event))
1237             {
1238                 rowDown(offset, event.Source);
1239             }
1240             else if (isMoveUp(event))
1241             {
1242                 rowUp(offset, event.Source);
1243             }
1244             else if (isDown(event))
1245             {
1246                 cursorDown(offset, event.Source);
1247             }
1248             else if (isUp(event))
1249             {
1250                 cursorUp(offset, event.Source);
1251             }
1252             enableButtons();
1253         }
1254 
1255         /**
1256          * returns the column number of the given control.
1257          * The given control must belong to this row or
1258          * an IllegalArgumentException will accure.
1259          * @param control
1260          * @return an int columnd number of the given control (0 to 3).
1261          */
1262         private int getColumn(Object control)
1263         {
1264             if (control == textbox)
1265             {
1266                 return 1;
1267             }
1268             else if (control == combobox)
1269             {
1270                 return 2;
1271             }
1272             else if (control == timebox)
1273             {
1274                 return 3;
1275             }
1276             else if (control == label)
1277             {
1278                 return 0;
1279             }
1280             else
1281             {
1282                 throw new IllegalArgumentException("Control is not part of this ControlRow");
1283             }
1284         }
1285 
1286         private boolean isMoveDown(KeyEvent e)
1287         {
1288             return (e.KeyCode == Key.DOWN) && (e.Modifiers == KeyModifier.MOD1);
1289         }
1290 
1291         private boolean isMoveUp(KeyEvent e)
1292         {
1293             return (e.KeyCode == Key.UP) && (e.Modifiers == KeyModifier.MOD1);
1294         }
1295 
1296         private boolean isDown(KeyEvent e)
1297         {
1298             return (e.KeyCode == Key.DOWN) && (e.Modifiers == 0);
1299         }
1300 
1301         private boolean isUp(KeyEvent e)
1302         {
1303             return (e.KeyCode == Key.UP) && (e.Modifiers == 0);
1304         }
1305 
1306         public void keyReleased(KeyEvent arg0)
1307         {
1308         }
1309 
1310 
1311         /* (non-Javadoc)
1312          * @see com.sun.star.lang.XEventListener#disposing(com.sun.star.lang.EventObject)
1313          */
1314         public void disposing(EventObject arg0)
1315         {
1316         }
1317     }
1318 }
1319