001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.editors.messagebundle; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.i18n.CmsMessages; 033import org.opencms.main.CmsLog; 034import org.opencms.main.OpenCms; 035import org.opencms.search.CmsSearchException; 036import org.opencms.search.solr.CmsSolrIndex; 037import org.opencms.search.solr.CmsSolrQuery; 038import org.opencms.search.solr.CmsSolrResultList; 039import org.opencms.ui.FontOpenCms; 040import org.opencms.ui.components.extensions.CmsAutoGrowingTextArea; 041 042import java.io.Serializable; 043import java.util.ArrayList; 044import java.util.Collection; 045import java.util.HashMap; 046import java.util.HashSet; 047import java.util.List; 048import java.util.Locale; 049import java.util.Map; 050import java.util.Set; 051 052import org.apache.commons.logging.Log; 053 054import org.tepi.filtertable.FilterTable; 055 056import com.vaadin.event.Action; 057import com.vaadin.event.Action.Handler; 058import com.vaadin.event.FieldEvents.BlurEvent; 059import com.vaadin.event.FieldEvents.BlurListener; 060import com.vaadin.event.FieldEvents.FocusEvent; 061import com.vaadin.event.FieldEvents.FocusListener; 062import com.vaadin.event.ShortcutAction; 063import com.vaadin.ui.Button; 064import com.vaadin.ui.Button.ClickEvent; 065import com.vaadin.ui.Button.ClickListener; 066import com.vaadin.ui.Component; 067import com.vaadin.ui.Notification; 068import com.vaadin.ui.Notification.Type; 069import com.vaadin.ui.UI; 070import com.vaadin.v7.data.Container; 071import com.vaadin.v7.data.Property; 072import com.vaadin.v7.data.Property.ValueChangeEvent; 073import com.vaadin.v7.data.validator.AbstractStringValidator; 074import com.vaadin.v7.ui.AbstractTextField; 075import com.vaadin.v7.ui.DefaultFieldFactory; 076import com.vaadin.v7.ui.Field; 077import com.vaadin.v7.ui.HorizontalLayout; 078import com.vaadin.v7.ui.Table; 079import com.vaadin.v7.ui.Table.CellStyleGenerator; 080import com.vaadin.v7.ui.TextArea; 081import com.vaadin.v7.ui.TextField; 082 083/** Types and helper classes used by the message bundle editor. */ 084public final class CmsMessageBundleEditorTypes { 085 086 /** Types of bundles editable by the Editor. */ 087 public enum BundleType { 088 /** A bundle of type propertyvfsbundle. */ 089 PROPERTY, 090 /** A bundle of type xmlvfsbundle. */ 091 XML, 092 /** A bundle descriptor. */ 093 DESCRIPTOR; 094 095 /** 096 * An adjusted version of what is typically Enum.valueOf(). 097 * @param value the resource type name that should be transformed into BundleType 098 * @return The bundle type for the resource type name, or null, if the resource has no bundle type. 099 */ 100 public static BundleType toBundleType(String value) { 101 102 if (null == value) { 103 return null; 104 } 105 if (value.equals(PROPERTY.toString())) { 106 return PROPERTY; 107 } 108 if (value.equals(XML.toString())) { 109 return XML; 110 } 111 if (value.equals(DESCRIPTOR.toString())) { 112 return DESCRIPTOR; 113 } 114 115 return null; 116 } 117 118 /** 119 * @see java.lang.Enum#toString() 120 */ 121 @Override 122 public String toString() { 123 124 switch (this) { 125 case PROPERTY: 126 return "propertyvfsbundle"; 127 case XML: 128 return "xmlvfsbundle"; 129 case DESCRIPTOR: 130 return "bundledescriptor"; 131 default: 132 throw new IllegalArgumentException(); 133 } 134 } 135 } 136 137 /** Helper for accessing Bundle descriptor XML contents. */ 138 public static final class Descriptor { 139 140 /** Message node. */ 141 public static final String N_MESSAGE = "Message"; 142 /** Key node. */ 143 public static final String N_KEY = "Key"; 144 /** Description node. */ 145 public static final String N_DESCRIPTION = "Description"; 146 /** Default node. */ 147 public static final String N_DEFAULT = "Default"; 148 /** Locale in which the content is available. */ 149 public static final Locale LOCALE = new Locale("en"); 150 /** The mandatory postfix of a bundle descriptor. */ 151 public static final String POSTFIX = "_desc"; 152 153 } 154 155 /** The propertyIds of the table columns. */ 156 public enum TableProperty { 157 /** Table column with the message key. */ 158 KEY, 159 /** Table column with the message description. */ 160 DESCRIPTION, 161 /** Table column with the message's default value. */ 162 DEFAULT, 163 /** Table column with the current (language specific) translation of the message. */ 164 TRANSLATION, 165 /** Table column with the options (add, delete). */ 166 OPTIONS 167 } 168 169 /** 170 * Data stored for each editable field in the message table. 171 */ 172 static class ComponentData implements Serializable { 173 174 /** Serialization id. */ 175 private static final long serialVersionUID = 1L; 176 177 /** Id of the editable column. */ 178 private int m_editableId; 179 /** Id of the table row. */ 180 private Object m_itemId; 181 /** The value in the field when it gets the focus, i.e., before a current edit operation. */ 182 private String m_lastValue; 183 184 /** 185 * Default constructor. 186 * 187 * @param editableId id of the editable column. 188 * @param itemId id of the table row. 189 * @param lastValue the value in the field when it gets the focus, i.e., before a current edit operation. 190 */ 191 public ComponentData(int editableId, Object itemId, String lastValue) { 192 193 m_editableId = editableId; 194 m_itemId = itemId; 195 m_lastValue = lastValue; 196 } 197 198 /** 199 * Returns the editable column id. 200 * @return the editable column id. 201 */ 202 public int getEditableColumnId() { 203 204 return m_editableId; 205 } 206 207 /** 208 * Returns the id of the table row. 209 * @return the id of the table row. 210 */ 211 public Object getItemId() { 212 213 return m_itemId; 214 } 215 216 /** 217 * Returns the last value in the field (before the current edit operation). 218 * @return the last value in the field (before the current edit operation). 219 */ 220 public String getLastValue() { 221 222 return m_lastValue; 223 } 224 225 /** 226 * Set the last value in the field. Do this when the field is focused. 227 * @param lastValue the last value in the field. 228 */ 229 public void setLastValue(String lastValue) { 230 231 m_lastValue = lastValue; 232 } 233 } 234 235 /** The different edit modes. */ 236 enum EditMode { 237 /** Editing the messages and the descriptor. */ 238 MASTER, 239 /** Only editing messages. */ 240 DEFAULT 241 } 242 243 /** 244 * The editor state holds the information on what columns of the editors table 245 * should be editable and if the options column should be shown. 246 * The state depends on the loaded bundle and the edit mode. 247 */ 248 static class EditorState { 249 250 /** The editable columns (from left to right).*/ 251 private List<TableProperty> m_editableColumns; 252 /** Flag, indicating if the options column should be shown. */ 253 private boolean m_showOptions; 254 255 /** Constructor, setting all the state information directly. 256 * @param editableColumns the property ids of the editable columns (from left to right) 257 * @param showOptions flag, indicating if the options column should be shown. 258 */ 259 public EditorState(List<TableProperty> editableColumns, boolean showOptions) { 260 261 m_editableColumns = editableColumns; 262 m_showOptions = showOptions; 263 } 264 265 /** Returns the editable columns from left to right (as there property ids). 266 * @return the editable columns from left to right (as there property ids). 267 */ 268 public List<TableProperty> getEditableColumns() { 269 270 return m_editableColumns; 271 } 272 273 /** Returns a flag, indicating if the options column should be shown. 274 * @return a flag, indicating if the options column should be shown. 275 */ 276 public boolean isShowOptions() { 277 278 return m_showOptions; 279 } 280 } 281 282 /** Key change event. */ 283 static class EntryChangeEvent { 284 285 /** The field via which the key was edited. */ 286 private AbstractTextField m_source; 287 /** The item id of the table row in which the key was edited. */ 288 private Object m_itemId; 289 /** The property id the table column in which the value was edited. */ 290 private Object m_propertyId; 291 /** The value before it was edited. */ 292 private String m_oldValue; 293 /** The value after it was edited. */ 294 private String m_newValue; 295 296 /** Default constructor. 297 * @param source the field via which the entry was edited. 298 * @param itemId the item id of the table row in which the entry was edited. 299 * @param propertyId the property id of the table column in which the entry was edited 300 * @param oldKey the key before it was edited. 301 * @param newKey the key after it was edited. 302 */ 303 public EntryChangeEvent( 304 AbstractTextField source, 305 Object itemId, 306 Object propertyId, 307 String oldKey, 308 String newKey) { 309 310 m_source = source; 311 m_itemId = itemId; 312 m_propertyId = propertyId; 313 m_oldValue = oldKey; 314 m_newValue = newKey; 315 } 316 317 /** 318 * Returns the item id of the table row in which the entry was edited. 319 * @return the item id of the table row in which the entry was edited. 320 */ 321 public Object getItemId() { 322 323 return m_itemId; 324 } 325 326 /** 327 * Returns the value after it was edited. 328 * @return the value after it was edited. 329 */ 330 public String getNewValue() { 331 332 return m_newValue; 333 } 334 335 /** 336 * Returns the value before it was edited. 337 * @return the value before it was edited. 338 */ 339 public String getOldValue() { 340 341 return m_oldValue; 342 } 343 344 /** 345 * Returns the property id of the table column in which the entry was edited. 346 * @return the property id of the table column in which the entry was edited. 347 */ 348 public Object getPropertyId() { 349 350 return m_propertyId; 351 } 352 353 /** 354 * Returns the field via which the entry was edited. 355 * @return the field via which the entry was edited. 356 */ 357 public AbstractTextField getSource() { 358 359 return m_source; 360 } 361 } 362 363 /** Interface for a entry change handler. */ 364 static interface I_EntryChangeListener { 365 366 /** 367 * Called when a entry change event is fired. 368 * @param event the entry change event. 369 */ 370 void handleEntryChange(EntryChangeEvent event); 371 } 372 373 /** Interface for a item deletion listener. */ 374 static interface I_ItemDeletionListener { 375 376 /** 377 * Called when an item deletion event is fired. 378 * @param e the event 379 * @return <code>true</code> if deletion handling was successful, <code>false</code> otherwise. 380 */ 381 boolean handleItemDeletion(ItemDeletionEvent e); 382 } 383 384 /** Interface for an Listener for changes in the options. */ 385 static interface I_OptionListener { 386 387 /** 388 * Handles adding a key. 389 * @param key the key to add. 390 * @return flag, indicating if the key was added. If not, it was already present. 391 */ 392 boolean handleAddKey(String key); 393 394 /** 395 * Handles a language change. 396 * @param language the newly selected language. 397 */ 398 void handleLanguageChange(Locale language); 399 400 /** 401 * Handles the change of the edit mode. 402 * @param mode the newly selected edit mode. 403 */ 404 void handleModeChange(EditMode mode); 405 406 } 407 408 /** Item deletion event. */ 409 static class ItemDeletionEvent { 410 411 /** The id of the deleted item. */ 412 private Object m_itemId; 413 414 /** Default constructor. 415 * @param itemId the id of the deleted item. 416 */ 417 public ItemDeletionEvent(Object itemId) { 418 419 m_itemId = itemId; 420 } 421 422 /** 423 * Returns the id of the deleted item. 424 * @return the id of the deleted item. 425 */ 426 public Object getItemId() { 427 428 return m_itemId; 429 } 430 431 } 432 433 /** Manages the keys used in at least one locale. */ 434 static final class KeySet { 435 436 /** Map from keys to the number of locales they are present. */ 437 Map<Object, Integer> m_keyset; 438 439 /** Default constructor. */ 440 public KeySet() { 441 442 m_keyset = new HashMap<Object, Integer>(); 443 } 444 445 /** 446 * Returns the current key set. 447 * @return the current key set. 448 */ 449 public Set<Object> getKeySet() { 450 451 return new HashSet<Object>(m_keyset.keySet()); 452 } 453 454 /** 455 * Removes the given key. 456 * @param key the key to be removed. 457 */ 458 public void removeKey(final String key) { 459 460 m_keyset.remove(key); 461 } 462 463 /** 464 * Rename a key. 465 * @param oldKey the current key name. 466 * @param newKey the substitution for the key name. 467 */ 468 public void renameKey(String oldKey, String newKey) { 469 470 if (m_keyset.containsKey(oldKey) && !m_keyset.containsKey(newKey)) { 471 Integer count = m_keyset.get(oldKey); 472 m_keyset.remove(oldKey); 473 m_keyset.put(newKey, count); 474 } else { 475 //TODO: should never be the case, but handle it anyway? 476 } 477 478 } 479 480 /** 481 * Updates the set with all keys that are used in at least one language. 482 * @param oldKeys keys of a locale as registered before 483 * @param newKeys keys of the locale now 484 */ 485 public void updateKeySet(Set<Object> oldKeys, Set<Object> newKeys) { 486 487 // Remove keys that are not present anymore 488 if (null != oldKeys) { 489 Set<Object> removedKeys = new HashSet<Object>(oldKeys); 490 if (null != newKeys) { 491 removedKeys.removeAll(newKeys); 492 } 493 for (Object key : removedKeys) { 494 Integer i = m_keyset.get(key); 495 int uses = null != i ? i.intValue() : 0; 496 if (uses > 1) { 497 m_keyset.put(key, Integer.valueOf(uses - 1)); 498 } else if (uses == 1) { 499 m_keyset.remove(key); 500 } 501 } 502 } 503 504 // Add keys that are new 505 if (null != newKeys) { 506 Set<Object> addedKeys = new HashSet<Object>(newKeys); 507 if (null != oldKeys) { 508 addedKeys.removeAll(oldKeys); 509 } 510 for (Object key : addedKeys) { 511 if (m_keyset.containsKey(key)) { 512 m_keyset.put(key, Integer.valueOf(m_keyset.get(key).intValue() + 1)); 513 } else { 514 m_keyset.put(key, Integer.valueOf(1)); 515 } 516 } 517 } 518 519 } 520 521 } 522 523 /** Validates keys. */ 524 @SuppressWarnings("serial") 525 static class KeyValidator extends AbstractStringValidator { 526 527 /** 528 * Default constructor. 529 */ 530 public KeyValidator() { 531 532 super(Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_INVALID_KEY_0)); 533 534 } 535 536 /** 537 * @see com.vaadin.data.validator.AbstractValidator#isValidValue(java.lang.Object) 538 */ 539 @Override 540 protected boolean isValidValue(String value) { 541 542 if (null == value) { 543 return true; 544 } 545 return !value.matches(".*\\p{IsWhite_Space}.*"); 546 } 547 548 } 549 550 /** A column generator that additionally adjusts the appearance of the options buttons to selection changes on the table. */ 551 @SuppressWarnings("serial") 552 static class OptionColumnGenerator implements com.vaadin.v7.ui.Table.ColumnGenerator { 553 554 /** Map from itemId (row) -> option buttons in the row. */ 555 Map<Object, Collection<Component>> m_buttons; 556 /** The id of the currently selected item (row). */ 557 Object m_selectedItem; 558 /** The table, the column is generated for. */ 559 FilterTable m_table; 560 /** The key deletion listeners. */ 561 I_ItemDeletionListener m_listener; 562 563 /** 564 * Default constructor. 565 * 566 * @param table the table, for which the column is generated for. 567 */ 568 public OptionColumnGenerator(final FilterTable table) { 569 570 m_buttons = new HashMap<Object, Collection<Component>>(); 571 m_table = table; 572 m_table.addValueChangeListener(new Property.ValueChangeListener() { 573 574 public void valueChange(ValueChangeEvent event) { 575 576 selectItem(m_table.getValue()); 577 } 578 }); 579 580 } 581 582 public Object generateCell(final Table source, final Object itemId, final Object columnId) { 583 584 CmsMessages messages = Messages.get().getBundle(UI.getCurrent().getLocale()); 585 HorizontalLayout options = new HorizontalLayout(); 586 Button delete = new Button(); 587 delete.addStyleName("icon-only"); 588 delete.addStyleName("borderless-colored"); 589 delete.setDescription(messages.key(Messages.GUI_REMOVE_ROW_0)); 590 delete.setIcon(FontOpenCms.CIRCLE_MINUS, messages.key(Messages.GUI_REMOVE_ROW_0)); 591 delete.addClickListener(new ClickListener() { 592 593 public void buttonClick(ClickEvent event) { 594 595 ItemDeletionEvent e = new ItemDeletionEvent(itemId); 596 if ((null == m_listener) || m_listener.handleItemDeletion(e)) { 597 m_table.removeItem(itemId); 598 } 599 } 600 }); 601 602 options.addComponent(delete); 603 604 Collection<Component> buttons = new ArrayList<Component>(1); 605 buttons.add(delete); 606 m_buttons.put(itemId, buttons); 607 608 if (source.isSelected(itemId)) { 609 selectItem(itemId); 610 } 611 612 return options; 613 } 614 615 /** 616 * Registers an item deletion listener. Only one listener can be registered. 617 * Registering a new listener will automatically unregister the previous one. 618 * 619 * @param listener the listener to register. 620 */ 621 void registerItemDeletionListener(final I_ItemDeletionListener listener) { 622 623 m_listener = listener; 624 } 625 626 /** 627 * Call this method, when a new item is selected. It will adjust the style of the option buttons, thus that they stay visible. 628 * 629 * @param itemId the id of the newly selected item (row). 630 */ 631 void selectItem(final Object itemId) { 632 633 if ((null != m_selectedItem) && (null != m_buttons.get(m_selectedItem))) { 634 for (Component button : m_buttons.get(m_selectedItem)) { 635 button.removeStyleName("borderless"); 636 button.addStyleName("borderless-colored"); 637 638 } 639 } 640 m_selectedItem = itemId; 641 if ((null != m_selectedItem) && (null != m_buttons.get(m_selectedItem))) { 642 for (Component button : m_buttons.get(m_selectedItem)) { 643 button.removeStyleName("borderless-colored"); 644 button.addStyleName("borderless"); 645 } 646 } 647 } 648 649 } 650 651 /** Handler to improve the keyboard navigation in the table. */ 652 @SuppressWarnings("serial") 653 static class TableKeyboardHandler implements Handler { 654 655 /** The field factory keeps track of the editable rows and the row/col positions of the TextFields. */ 656 private FilterTable m_table; 657 658 /** Tab was pressed. */ 659 private Action m_tabNext = new ShortcutAction("Tab", ShortcutAction.KeyCode.TAB, null); 660 /** Tab+Shift was pressed. */ 661 private Action m_tabPrev = new ShortcutAction( 662 "Shift+Tab", 663 ShortcutAction.KeyCode.TAB, 664 new int[] {ShortcutAction.ModifierKey.SHIFT}); 665 /** Down was pressed. */ 666 private Action m_curDown = new ShortcutAction("Down", ShortcutAction.KeyCode.ARROW_DOWN, null); 667 /** Up was pressed. */ 668 private Action m_curUp = new ShortcutAction("Up", ShortcutAction.KeyCode.ARROW_UP, null); 669 /** Enter was pressed. */ 670 private Action m_enter = new ShortcutAction("Enter", ShortcutAction.KeyCode.ENTER, null); 671 672 /** 673 * Shortcut-Handler to improve the navigation in the table component. 674 * 675 * @param table the table, the handler is attached to. 676 */ 677 public TableKeyboardHandler(final FilterTable table) { 678 679 m_table = table; 680 } 681 682 /** 683 * @see com.vaadin.event.Action.Handler#getActions(java.lang.Object, java.lang.Object) 684 */ 685 public Action[] getActions(Object target, Object sender) { 686 687 return new Action[] {m_tabNext, m_tabPrev, m_curDown, m_curUp, m_enter}; 688 } 689 690 /** 691 * @see com.vaadin.event.Action.Handler#handleAction(com.vaadin.event.Action, java.lang.Object, java.lang.Object) 692 */ 693 public void handleAction(Action action, Object sender, Object target) { 694 695 TranslateTableFieldFactory fieldFactory = (TranslateTableFieldFactory)m_table.getTableFieldFactory(); 696 List<TableProperty> editableColums = fieldFactory.getEditableColumns(); 697 698 if (target instanceof AbstractTextField) { 699 // Move according to keypress 700 ComponentData data = (ComponentData)(((AbstractTextField)target).getData()); 701 // Abort if no data attribute found 702 if (null == data) { 703 return; 704 } 705 int colId = data.getEditableColumnId(); 706 Integer rowIdInteger = (Integer)data.getItemId(); 707 @SuppressWarnings("boxing") // rowIdInteger should never be null 708 int rowId = Integer.valueOf(rowIdInteger); 709 710 // TODO: Find a better solution? 711 // NOTE: A collection is returned, but actually it's a linked list. 712 // It's a hack, but actually I don't know how to do better here. 713 @SuppressWarnings("unchecked") 714 List<Integer> visibleItemIds = (List<Integer>)m_table.getVisibleItemIds(); 715 716 if ((action == m_curDown) || (action == m_enter)) { 717 int currentRow = visibleItemIds.indexOf(Integer.valueOf(rowId)); 718 if (currentRow < (visibleItemIds.size() - 1)) { 719 rowId = visibleItemIds.get(currentRow + 1).intValue(); 720 } 721 } else if (action == m_curUp) { 722 int currentRow = visibleItemIds.indexOf(Integer.valueOf(rowId)); 723 if (currentRow > 0) { 724 rowId = visibleItemIds.get(currentRow - 1).intValue(); 725 } 726 } else if (action == m_tabNext) { 727 int nextColId = getNextColId(editableColums, colId); 728 if (colId >= nextColId) { 729 int currentRow = visibleItemIds.indexOf(Integer.valueOf(rowId)); 730 rowId = visibleItemIds.get((currentRow + 1) % visibleItemIds.size()).intValue(); 731 } 732 colId = nextColId; 733 } else if (action == m_tabPrev) { 734 int previousColId = getPreviousColId(editableColums, colId); 735 if (colId <= previousColId) { 736 int currentRow = visibleItemIds.indexOf(Integer.valueOf(rowId)); 737 rowId = visibleItemIds.get( 738 ((currentRow + visibleItemIds.size()) - 1) % visibleItemIds.size()).intValue(); 739 } 740 colId = previousColId; 741 } 742 743 AbstractTextField newTF = fieldFactory.getValueFields().get(Integer.valueOf(colId)).get( 744 Integer.valueOf(rowId)); 745 if (newTF != null) { 746 newTF.focus(); 747 } 748 } 749 } 750 751 /** 752 * Calculates the id of the next editable column. 753 * @param editableColumns all editable columns 754 * @param colId id (index in <code>editableColumns</code> plus 1) of the current column. 755 * @return id of the next editable column. 756 */ 757 private int getNextColId(List<TableProperty> editableColumns, int colId) { 758 759 for (int i = colId % editableColumns.size(); i != (colId - 1); i = (i + 1) % editableColumns.size()) { 760 if (!m_table.isColumnCollapsed(editableColumns.get(i))) { 761 return i + 1; 762 } 763 } 764 return colId; 765 } 766 767 /** 768 * Calculates the id of the previous editable column. 769 * @param editableColumns all editable columns 770 * @param colId id (index in <code>editableColumns</code> plus 1) of the current column. 771 * @return id of the previous editable column. 772 */ 773 private int getPreviousColId(List<TableProperty> editableColumns, int colId) { 774 775 // use +4 instead of -1 to prevent negativ numbers 776 for (int i = ((colId + editableColumns.size()) - 2) % editableColumns.size(); i != (colId 777 - 1); i = ((i + editableColumns.size()) - 1) % editableColumns.size()) { 778 if (!m_table.isColumnCollapsed(editableColumns.get(i))) { 779 return i + 1; 780 } 781 } 782 return colId; 783 } 784 } 785 786 /** Custom cell style generator to allow different style for editable columns. */ 787 @SuppressWarnings("serial") 788 static class TranslateTableCellStyleGenerator implements CellStyleGenerator { 789 790 /** The editable columns. */ 791 private List<TableProperty> m_editableColums; 792 793 /** 794 * Default constructor, taking the list of editable columns. 795 * 796 * @param editableColumns the list of editable columns. 797 */ 798 public TranslateTableCellStyleGenerator(List<TableProperty> editableColumns) { 799 800 m_editableColums = editableColumns; 801 if (null == m_editableColums) { 802 m_editableColums = new ArrayList<TableProperty>(); 803 } 804 } 805 806 /** 807 * @see com.vaadin.ui.CustomTable.CellStyleGenerator#getStyle(com.vaadin.ui.CustomTable, java.lang.Object, java.lang.Object) 808 */ 809 public String getStyle(Table source, Object itemId, Object propertyId) { 810 811 String result = TableProperty.KEY.equals(propertyId) ? "key-" : ""; 812 result += m_editableColums.contains(propertyId) ? "editable" : "fix"; 813 return result; 814 } 815 816 } 817 818 /** TableFieldFactory for making only some columns editable and to support enhanced navigation. */ 819 @SuppressWarnings("serial") 820 static class TranslateTableFieldFactory extends DefaultFieldFactory { 821 822 /** Mapping from column -> row -> AbstractTextField. */ 823 private final Map<Integer, Map<Integer, AbstractTextField>> m_valueFields; 824 /** The editable columns. */ 825 private final List<TableProperty> m_editableColumns; 826 /** Reference to the table, the factory is used for. */ 827 final FilterTable m_table; 828 /** Registered key change listeners. */ 829 private final Set<I_EntryChangeListener> m_keyChangeListeners = new HashSet<I_EntryChangeListener>(); 830 831 /** 832 * Default constructor. 833 * @param table The table, the factory is used for. 834 * @param editableColumns the property names of the editable columns of the table. 835 */ 836 public TranslateTableFieldFactory(FilterTable table, List<TableProperty> editableColumns) { 837 838 m_table = table; 839 m_valueFields = new HashMap<Integer, Map<Integer, AbstractTextField>>(); 840 m_editableColumns = editableColumns; 841 } 842 843 /** 844 * @see com.vaadin.ui.TableFieldFactory#createField(com.vaadin.data.Container, java.lang.Object, java.lang.Object, com.vaadin.ui.Component) 845 */ 846 @Override 847 public Field<?> createField( 848 final Container container, 849 final Object itemId, 850 final Object propertyId, 851 Component uiContext) { 852 853 final TableProperty pid = (TableProperty)propertyId; 854 855 for (int i = 1; i <= m_editableColumns.size(); i++) { 856 if (pid.equals(m_editableColumns.get(i - 1))) { 857 858 AbstractTextField tf; 859 if (pid.equals(TableProperty.KEY)) { 860 tf = new TextField(); 861 tf.addValidator(new KeyValidator()); 862 } else { 863 TextArea atf = new TextArea(); 864 atf.setRows(1); 865 CmsAutoGrowingTextArea.addTo(atf, 20); 866 tf = atf; 867 } 868 tf.setWidth("100%"); 869 tf.setResponsive(true); 870 871 tf.setInputPrompt( 872 Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_PLEASE_ADD_VALUE_0)); 873 tf.setData(new ComponentData(i, itemId, "")); 874 if (!m_valueFields.containsKey(Integer.valueOf(i))) { 875 m_valueFields.put(Integer.valueOf(i), new HashMap<Integer, AbstractTextField>()); 876 } 877 m_valueFields.get(Integer.valueOf(i)).put((Integer)itemId, tf); 878 tf.addFocusListener(new FocusListener() { 879 880 public void focus(FocusEvent event) { 881 882 if (!m_table.isSelected(itemId)) { 883 m_table.select(itemId); 884 } 885 AbstractTextField field = (AbstractTextField)event.getComponent(); 886 // Update last value 887 ComponentData data = (ComponentData)field.getData(); 888 data.setLastValue(field.getValue()); 889 field.setData(data); 890 } 891 892 }); 893 tf.addBlurListener(new BlurListener() { 894 895 public void blur(BlurEvent event) { 896 897 AbstractTextField field = (AbstractTextField)event.getComponent(); 898 ComponentData data = (ComponentData)field.getData(); 899 if (!data.getLastValue().equals(field.getValue())) { 900 EntryChangeEvent ev = new EntryChangeEvent( 901 field, 902 data.getItemId(), 903 pid, 904 data.getLastValue(), 905 field.getValue()); 906 fireKeyChangeEvent(ev); 907 } 908 } 909 }); 910 return tf; 911 } 912 } 913 return null; 914 915 } 916 917 /** 918 * Returns the editable columns. 919 * @return the editable columns. 920 */ 921 public List<TableProperty> getEditableColumns() { 922 923 return m_editableColumns; 924 } 925 926 /** 927 * Returns the mapping from the position in the table to the TextField. 928 * @return the mapping from the position in the table to the TextField. 929 */ 930 public Map<Integer, Map<Integer, AbstractTextField>> getValueFields() { 931 932 return m_valueFields; 933 } 934 935 /** 936 * Register a key change listener. 937 * @param listener the listener to register. 938 */ 939 public void registerKeyChangeListener(final I_EntryChangeListener listener) { 940 941 m_keyChangeListeners.add(listener); 942 } 943 944 /** 945 * Called to fire a key change event. 946 * @param ev the event to fire. 947 */ 948 void fireKeyChangeEvent(final EntryChangeEvent ev) { 949 950 for (I_EntryChangeListener listener : m_keyChangeListeners) { 951 listener.handleEntryChange(ev); 952 } 953 } 954 } 955 956 /** The log object for this class. */ 957 static final Log LOG = CmsLog.getLog(CmsMessageBundleEditorTypes.class); 958 959 /** The width of the options column in pixel. */ 960 public static final int OPTION_COLUMN_WIDTH = 42; 961 962 /** The width of the options column in pixel. */ 963 public static final String OPTION_COLUMN_WIDTH_PX = OPTION_COLUMN_WIDTH + "px"; 964 965 /** Hide default constructor. */ 966 private CmsMessageBundleEditorTypes() { 967 //noop 968 } 969 970 /** 971 * Returns the bundle descriptor for the bundle with the provided base name. 972 * @param cms {@link CmsObject} used for searching. 973 * @param basename the bundle base name, for which the descriptor is searched. 974 * @return the bundle descriptor, or <code>null</code> if it does not exist or searching fails. 975 */ 976 public static CmsResource getDescriptor(CmsObject cms, String basename) { 977 978 CmsSolrQuery query = new CmsSolrQuery(); 979 query.setResourceTypes(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR.toString()); 980 query.setFilterQueries("filename:\"" + basename + CmsMessageBundleEditorTypes.Descriptor.POSTFIX + "\""); 981 query.add("fl", "path"); 982 CmsSolrResultList results; 983 try { 984 boolean isOnlineProject = cms.getRequestContext().getCurrentProject().isOnlineProject(); 985 String indexName = isOnlineProject 986 ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE 987 : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE; 988 results = OpenCms.getSearchManager().getIndexSolr(indexName).search(cms, query, true, null, true, null); 989 } catch (CmsSearchException e) { 990 LOG.error(Messages.get().getBundle().key(Messages.ERR_BUNDLE_DESCRIPTOR_SEARCH_ERROR_0), e); 991 return null; 992 } 993 994 switch (results.size()) { 995 case 0: 996 return null; 997 case 1: 998 return results.get(0); 999 default: 1000 String files = ""; 1001 for (CmsResource res : results) { 1002 files += " " + res.getRootPath(); 1003 } 1004 LOG.warn(Messages.get().getBundle().key(Messages.ERR_BUNDLE_DESCRIPTOR_NOT_UNIQUE_1, files)); 1005 return results.get(0); 1006 } 1007 } 1008 1009 /** 1010 * Displays a localized warning. 1011 * @param caption the caption of the warning. 1012 * @param description the description of the warning. 1013 */ 1014 static void showWarning(final String caption, final String description) { 1015 1016 Notification warning = new Notification(caption, description, Type.WARNING_MESSAGE, true); 1017 warning.setDelayMsec(-1); 1018 warning.show(UI.getCurrent().getPage()); 1019 1020 } 1021}