/******************************************************************************* * Copyright (c) 2012 Martin Reiterer. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * Contributors: * Martin Reiterer - initial API and implementation * Christian Behon - refactor from e3 to e4 ******************************************************************************/ package org.eclipselabs.e4.tapiji.translator.ui.treeviewer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.stream.Stream; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.e4.core.commands.ECommandService; import org.eclipse.e4.core.commands.EHandlerService; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.annotations.Creatable; import org.eclipse.e4.ui.di.Focus; import org.eclipse.e4.ui.services.EMenuService; import org.eclipse.e4.ui.workbench.modeling.ESelectionService; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.TreeViewerEditor; import org.eclipse.jface.viewers.TreeViewerFocusCellManager; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipselabs.e4.tapiji.logger.Log; import org.eclipselabs.e4.tapiji.translator.model.Glossary; import org.eclipselabs.e4.tapiji.translator.model.Term; import org.eclipselabs.e4.tapiji.translator.model.Translation; import org.eclipselabs.e4.tapiji.translator.preference.StoreInstanceState; import org.eclipselabs.e4.tapiji.translator.ui.treeviewer.provider.TreeViewerContentProvider; import org.eclipselabs.e4.tapiji.translator.ui.treeviewer.provider.TreeViewerLabelProvider; import org.eclipselabs.e4.tapiji.translator.ui.widget.dnd.GlossaryDragSource; import org.eclipselabs.e4.tapiji.translator.ui.widget.dnd.GlossaryDropTarget; import org.eclipselabs.e4.tapiji.translator.ui.widget.dnd.TermTransfer; import org.eclipselabs.e4.tapiji.translator.ui.widget.filter.ExactMatcher; import org.eclipselabs.e4.tapiji.translator.ui.widget.filter.FuzzyMatcher; import org.eclipselabs.e4.tapiji.translator.ui.widget.filter.SelectiveMatcher; import org.eclipselabs.e4.tapiji.translator.ui.widget.sorter.SortInfo; import org.eclipselabs.e4.tapiji.translator.ui.widget.sorter.TreeViewerSortOrder; import org.eclipselabs.e4.tapiji.utils.LocaleUtils; @Creatable @Singleton public final class TreeViewerView extends Composite implements IResourceChangeListener, TreeViewerContract.View { private static final String TAG = TreeViewerView.class.getSimpleName(); private static final String TREE_VIEWER_MENU_ID = "org.eclipselabs.e4.tapiji.translator.popupmenu.treeview"; private static final String TREE_VIEWER_MENU_EMPTY_ID = "org.eclipselabs.e4.tapiji.translator.popupmenu.treeview.empty"; private static final float DEFAULT_MATCHING_PRECISION = .75f; protected static final String COMMAND_DELETE_KEY = "org.eclipselabs.e4.tapiji.translator.command.removeTerm"; @Inject private StoreInstanceState storeInstanceState; @Inject private ESelectionService selectionService; @Inject private TreeViewerPresenter presenter; @Inject private EMenuService menuService; @Inject private ECommandService commandService; @Inject private EHandlerService handlerService; private boolean isColumnEditable; private boolean isFuzzyMatchingEnabled = false; private final boolean selectiveViewEnabled = false; private TreeViewer treeViewer; private SortInfo sortInfo; private String referenceLanguage; private TreeViewerLabelProvider treeViewerLabelProvider; private String[] translations; private TreeViewerSortOrder columnSorter; private ExactMatcher matcher; private final List<String> displayedTranslations = new ArrayList<String>(); private Tree tree; public TreeViewerView(Composite parent) { super(parent, SWT.FILL); final GridLayout gridLayout = new GridLayout(1, false); gridLayout.marginHeight = 0; gridLayout.marginWidth = 0; setLayout(gridLayout); setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); } @PostConstruct private void onCreate() { this.presenter.setView(this); this.translations = presenter.getGlossary().getTranslations(); this.treeViewer = new TreeViewer(this, SWT.BORDER | SWT.FULL_SELECTION | SWT.SINGLE); this.treeViewer.getTree().setHeaderVisible(false); this.treeViewer.getTree().setLinesVisible(false); this.treeViewer.setAutoExpandLevel(2); this.treeViewer.addSelectionChangedListener((event) -> { selectionService.setSelection(((IStructuredSelection) treeViewer.getSelection()).getFirstElement()); }); this.treeViewer.getTree().addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(MouseEvent event) { final Object element = selectionService.getSelection(); if (treeViewer.isExpandable(element)) { if (treeViewer.getExpandedState(element)) { treeViewer.collapseToLevel(element, 1); } else { treeViewer.expandToLevel(element, 1); } } } }); this.treeViewer.getTree().addKeyListener(new KeyAdapter() { @SuppressWarnings("restriction") @Override public void keyReleased(KeyEvent event) { if (event.character == SWT.DEL) { handlerService.executeHandler(commandService.createCommand(COMMAND_DELETE_KEY, Collections.emptyMap())); } } }); this.tree = treeViewer.getTree(); this.tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); TreeViewerEditor.create(treeViewer, new TreeViewerFocusCellManager(treeViewer, new FocusCellOwnerDrawHighlighter(treeViewer)), createColumnActivationStrategy(), ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION | ColumnViewerEditor.KEEP_EDITOR_ON_DOUBLE_CLICK); dragAndDrop(); registerTreeMenu(TREE_VIEWER_MENU_EMPTY_ID); } private void initializeWidget() { if (translations.length > 0) { createLocaleColumns(); treeViewerLabelProvider = TreeViewerLabelProvider.newInstance(treeViewer, displayedTranslations, displayedTranslations.indexOf(referenceLanguage)); treeViewerLabelProvider.isSearchEnabled(isFuzzyMatchingEnabled); treeViewer.setLabelProvider(treeViewerLabelProvider); treeViewer.setContentProvider(TreeViewerContentProvider.newInstance()); tree.setHeaderVisible(true); tree.setLinesVisible(true); } else { tree.setHeaderVisible(false); tree.setLinesVisible(false); } } private void createLocaleColumns() { displayedTranslations.clear(); final TextCellEditor textCellEditor = new TextCellEditor(tree); int columnIndex = 0; createColumn(referenceLanguage, columnIndex, textCellEditor); Log.d(TAG, "" + columnIndex + " " + referenceLanguage); columnIndex++; displayedTranslations.add(referenceLanguage); Log.d(TAG, "" + columnIndex); for (final String languageCode : translations) { if (languageCode.equalsIgnoreCase(referenceLanguage)) { continue; } displayedTranslations.add(languageCode); createColumn(languageCode, columnIndex, textCellEditor); columnIndex++; Log.d(TAG, "" + columnIndex + " " + languageCode); } } private void createColumn(final String languageCode, final int columnIndex, final TextCellEditor textCellEditor) { final Locale locale = LocaleUtils.getLocaleFromLanguageCode(languageCode); final TreeViewerColumn column = new TreeViewerColumn(treeViewer, SWT.NONE); column.getColumn().setWidth(200); column.getColumn().setMoveable(false); column.getColumn().setResizable(true); column.getColumn().setText(locale.getDisplayName()); column.getColumn().addSelectionListener(createColumnSelectionListener(columnIndex)); column.setEditingSupport(createEditingSupportFor(textCellEditor, columnIndex, languageCode)); } private void showHideColumn(final String languageCode) { final String language = LocaleUtils.getLocaleFromLanguageCode(languageCode).getDisplayName(); Stream.of(tree.getColumns()).filter(column -> language.equals(column.getText())).forEach(column -> { if (column.getWidth() == 0) { column.setWidth(200); column.setResizable(true); } else { column.setWidth(0); column.setResizable(false); } }); } @Override public void registerTreeMenu(String menuId) { this.menuService.registerContextMenu(this.treeViewer.getControl(),menuId); } private void setTreeStructure(final boolean grouped) { ((TreeViewerContentProvider) treeViewer.getContentProvider()).setGrouped(grouped); if (treeViewer.getInput() == null) { treeViewer.setUseHashlookup(false); } } private void clearTreeViewer() { treeViewer.getTree().clearAll(true); for (final TreeColumn column : tree.getColumns()) { column.dispose(); } } private void initMatchers() { treeViewer.resetFilters(); if (isFuzzyMatchingEnabled) { matcher = new FuzzyMatcher(treeViewer); ((FuzzyMatcher) matcher).setMinimumSimilarity(DEFAULT_MATCHING_PRECISION); } else { matcher = new ExactMatcher(treeViewer); } matcher.setPattern((matcher != null) ? matcher.getPattern() : ""); if (this.selectiveViewEnabled) { new SelectiveMatcher(treeViewer); } } private void dragAndDrop() { final Transfer[] transferTypes = new Transfer[] {TermTransfer.getInstance()}; treeViewer.addDragSupport(DND.DROP_MOVE, transferTypes, GlossaryDragSource.create(treeViewer, presenter.getGlossary())); treeViewer.addDropSupport(DND.DROP_MOVE, transferTypes, GlossaryDropTarget.create(treeViewer)); } private void referenceLanguage() { if (!storeInstanceState.getReferenceLanguage().isEmpty()) { this.referenceLanguage = storeInstanceState.getReferenceLanguage(); Log.d(TAG, "REFERENCE LANGUAGE FROM STORAGE" + referenceLanguage); } else { this.referenceLanguage = translations[0]; storeInstanceState.setReferenceLanguage(referenceLanguage); Log.d(TAG, "REFERENCE USE DEFAULT" + referenceLanguage); } } private void columnSorter(final Glossary glossary) { columnSorter = new TreeViewerSortOrder(treeViewer, sortInfo, glossary.getIndexOfLocale(referenceLanguage), glossary.info.translations); treeViewer.setComparator(columnSorter); } private EditingSupport createEditingSupportFor(final TextCellEditor textCellEditor, final int columnCnt, final String languageCode) { return new EditingSupport(treeViewer) { @Override protected boolean canEdit(final Object element) { return isColumnEditable; } @Override protected CellEditor getCellEditor(final Object element) { return textCellEditor; } @Override protected Object getValue(final Object element) { return treeViewerLabelProvider.getColumnText(element, columnCnt); } @Override protected void setValue(final Object element, final Object value) { if (element instanceof Term) { treeViewer.setSelection(new StructuredSelection(element), true); final Translation translation = ((Term) element).getTranslation(languageCode); if (translation != null) { Log.d(TAG, "EDIT COLUMN:" + value); translation.value = (String) value; getViewer().update(element, null); saveGlossaryAsync(); } } } private void saveGlossaryAsync() { presenter.updateGlossary(((TreeViewerContentProvider) treeViewer.getContentProvider()).getGlossary()); } }; } private boolean isSearchTreeGrouped() { return (matcher.getPattern().trim().length() < 0) && (columnSorter.getSortInfo().getColumnIndex() == 0); } @Override public void enableFuzzyMatching(final boolean enable) { Log.d(TAG, String.format("Enable fuzzy logic: %s", enable)); isFuzzyMatchingEnabled = enable; String pattern = ""; if (matcher != null) { pattern = matcher.getPattern(); if (!isFuzzyMatchingEnabled && enable) { if ((matcher.getPattern().trim().length() > 1) && matcher.getPattern().startsWith("*") && matcher.getPattern().endsWith("*")) { pattern = pattern.substring(1).substring(0, pattern.length() - 2); } matcher.setPattern(null); } } initMatchers(); if (treeViewerLabelProvider != null) { treeViewerLabelProvider.isSearchEnabled(enable); } matcher.setPattern(pattern); treeViewer.refresh(); } @Override public void setSearchString(final String searchString) { if (null != matcher) { if (searchString.isEmpty()) { matcher.setPattern(null); setTreeStructure(true); treeViewerLabelProvider.isSearchEnabled(false); } else { matcher.setPattern(searchString); setTreeStructure(isSearchTreeGrouped()); treeViewerLabelProvider.isSearchEnabled(true); } tree.setRedraw(true); treeViewer.refresh(); } } @Override public void setMatchingPrecision(final float value) { if (matcher instanceof FuzzyMatcher) { ((FuzzyMatcher) matcher).setMinimumSimilarity(value); treeViewer.refresh(); } } @Override public void setReferenceLanguage(final String referenceLanguage) { this.referenceLanguage = referenceLanguage; } @Override public void resourceChanged(final IResourceChangeEvent event) { } @Override public TreeViewer getTreeViewer() { return treeViewer; } @Override public void showHideTranslationColumn(final String languageCode) { showHideColumn(languageCode); } @Override public void setColumnEditable(final boolean isEditable) { this.isColumnEditable = isEditable; } @Override public void updateView(final Glossary glossary) { if (glossary != null) { tree.setRedraw(false); clearTreeViewer(); translations = glossary.info.getTranslations(); referenceLanguage(); initializeWidget(); columnSorter(glossary); initMatchers(); treeViewer.setInput(glossary); registerTreeMenu(TREE_VIEWER_MENU_ID); tree.setRedraw(true); treeViewer.refresh(); } } @Focus public void focus() { treeViewer.getControl().setFocus(); } public static TreeViewerContract.View create(final Composite parent, IEclipseContext eclipseContext) { final TreeViewerView view = new TreeViewerView(parent); org.eclipse.e4.core.contexts.ContextInjectionFactory.inject(view, eclipseContext); return view; } private ColumnViewerEditorActivationStrategy createColumnActivationStrategy() { return new ColumnViewerEditorActivationStrategy(treeViewer) { @Override protected boolean isEditorActivationEvent(final ColumnViewerEditorActivationEvent event) { ViewerCell cell = (ViewerCell) event.getSource(); boolean isEditorActivationEvent; if (cell.getColumnIndex() == 1) { isEditorActivationEvent = event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == ' ') || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } else { isEditorActivationEvent = event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == ' ') || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } return isEditorActivationEvent; } }; } private SelectionListener createColumnSelectionListener(final int columnIndex) { return new SelectionAdapter() { @Override public void widgetSelected(final SelectionEvent selectionEvent) { updateColumnOrder(columnIndex); } private void updateColumnOrder(final int columnIndex) { if (columnSorter != null) { final SortInfo sortInfo = columnSorter.getSortInfo(); if (columnIndex == sortInfo.getColumnIndex()) { sortInfo.setDescending(!sortInfo.isDescending()); } else { sortInfo.setColumnIndex(columnIndex); sortInfo.setDescending(false); } columnSorter.setSortInfo(sortInfo); setTreeStructure(columnIndex == 0); treeViewer.refresh(); } } }; } @Override public void addSelection(Term term) { Log.d(TAG, "SELECTION" + term); treeViewer.setSelection(new StructuredSelection(term), true); } }