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