/******************************************************************************* * Copyright (c) 2011 BestSolution.at and others. * 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: * Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation *******************************************************************************/ package at.bestsolution.efxclipse.tooling.jdt.ui.internal.editors.outline; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jdt.internal.ui.propertiesfileeditor.PropertiesFileEditor; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.views.contentoutline.ContentOutlinePage; import org.osgi.service.prefs.BackingStoreException; import at.bestsolution.efxclipse.tooling.jdt.ui.internal.JavaFXUIPlugin; @SuppressWarnings("restriction") public class PropertyContentOutlinePage extends ContentOutlinePage { private List<Object> hierarchicalStructure = new ArrayList<Object>(); private List<Property> flatStructure = new ArrayList<Property>(); private Pair[] properties = new Pair[0]; private IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode("propertiesoutline"); private static final String PREF_SORTED = "sorted"; private static final String PREF_HIERARCHICAL = "hierarchical"; private Action sortAction; private Action hierarchicalAction; private String groupRegexp = "_|\\.|/"; private IDocument document; private PropertiesFileEditor editor; public PropertyContentOutlinePage(PropertiesFileEditor editor) { final IEditorInput input = editor.getEditorInput(); final IDocumentProvider provider = editor.getDocumentProvider(); this.editor = editor; document = provider.getDocument(input); document.addDocumentListener(new IDocumentListener() { public void documentChanged(DocumentEvent event) { try { setProperties(getPairs(event.fDocument)); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void documentAboutToBeChanged(DocumentEvent event) { } }); try { properties = getPairs(document); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } sortAction = new Action("", IAction.AS_CHECK_BOX) { @Override public void run() { preferences.putBoolean(PREF_SORTED, isChecked()); try { preferences.flush(); } catch (BackingStoreException e) { e.printStackTrace(); } } }; sortAction.setImageDescriptor(JavaFXUIPlugin.getDefault().getImageRegistry() .getDescriptor(JavaFXUIPlugin.ALPHASORT_ICON)); sortAction.setChecked(isSorted()); hierarchicalAction = new Action("", IAction.AS_CHECK_BOX) { @Override public void run() { preferences.putBoolean(PREF_HIERARCHICAL, isChecked()); try { preferences.flush(); } catch (BackingStoreException e) { e.printStackTrace(); } } }; hierarchicalAction.setImageDescriptor(JavaFXUIPlugin.getDefault() .getImageRegistry().getDescriptor(JavaFXUIPlugin.HIERACHICAL_ICON)); hierarchicalAction.setChecked(isHierarchical()); preferences .addPreferenceChangeListener(new IPreferenceChangeListener() { public void preferenceChange(PreferenceChangeEvent event) { if (event.getKey().equals(PREF_SORTED)) { boolean val = isSorted(); setSorted(val); sortAction.setChecked(val); } else if (event.getKey().equals(PREF_HIERARCHICAL)) { boolean val = isHierarchical(); setHierarchical(val); hierarchicalAction.setChecked(val); } } }); } public Pair[] getPairs(IDocument document) throws UnsupportedEncodingException, IOException { Properties p = new Properties(); p.load(new ByteArrayInputStream(document.get().getBytes("UTF-8"))); Pair[] pairs = new Pair[p.entrySet().size()]; int i = 0; for (Entry<Object, Object> e : p.entrySet()) { pairs[i++] = new Pair((String) e.getKey(), (String) e.getValue()); } return pairs; } @Override public void makeContributions(IMenuManager menuManager, IToolBarManager toolBarManager, IStatusLineManager statusLineManager) { super.makeContributions(menuManager, toolBarManager, statusLineManager); toolBarManager.add(hierarchicalAction); toolBarManager.add(sortAction); Action a = new Action("", IAction.AS_PUSH_BUTTON) { @Override public void run() { try { getTreeViewer().getTree().setRedraw(false); getTreeViewer().collapseAll(); } finally { getTreeViewer().getTree().setRedraw(true); } } }; a.setImageDescriptor(JavaFXUIPlugin.getDefault().getImageRegistry() .getDescriptor(JavaFXUIPlugin.COLLAPSE_ICON)); toolBarManager.add(a); } @Override protected void fireSelectionChanged(ISelection selection) { super.fireSelectionChanged(selection); Object o = ((IStructuredSelection) selection).getFirstElement(); String searchKey = null; int selectionLength = 0; if (o instanceof PropertyGroup) { searchKey = ((PropertyGroup) o).name; } else if (o instanceof Property) { searchKey = ((Property) o).pair.key; selectionLength = ((Property) o).pair.value.length(); } if (searchKey == null) { return; } int lines = document.getNumberOfLines(); try { for (int i = 0; i < lines; i++) { IRegion r = document.getLineInformation(i); String line = document.get(r.getOffset(), r.getLength()); if (line.startsWith(searchKey)) { editor.selectAndReveal(r.getOffset() + r.getLength() - selectionLength, selectionLength); break; } } } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void setSorted(boolean sorted) { // FIXME We should delay until visible! if (sorted) { getTreeViewer().setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { if (e1 instanceof PropertyGroup && e2 instanceof PropertyGroup) { return ((PropertyGroup) e1).name .compareTo(((PropertyGroup) e2).name); } else if (e1 instanceof Property && e2 instanceof Property) { String k1 = ((Property) e1).pair.key.replaceAll( groupRegexp, ""); String k2 = ((Property) e2).pair.key.replaceAll( groupRegexp, ""); return k1.compareTo(k2); } else if (e1 instanceof Property) { return -1; } else if (e2 instanceof Property) { return -1; } return super.compare(viewer, e1, e2); } }); } else { getTreeViewer().setComparator(null); } } protected void setHierarchical(boolean val) { // FIXME We should delay until visible! if (val) { getTreeViewer().setInput(hierarchicalStructure); } else { getTreeViewer().setInput(flatStructure); } } private void createHierarchicalStructure() { List<Property> found = new ArrayList<Property>(); List<Property> added = new ArrayList<Property>(); for (Pair property : properties) { boolean isFound = false; Iterator<Property> it = flatStructure.iterator(); while (it.hasNext()) { Property p = it.next(); if (p.pair.key.equals(property.key)) { p.pair = property; isFound = true; found.add(p); it.remove(); } } if (!isFound) { added.add(new Property(property)); } } // These are removed for (Property p : flatStructure) { if (p.parent != null) { p.parent.items.remove(p); p.parent = null; } else { hierarchicalStructure.remove(p); } } flatStructure.clear(); flatStructure.addAll(found); for (Property p : added) { List<String> parts = new ArrayList<String>(Arrays.asList(p.pair.key .split(groupRegexp))); if (parts.size() > 1) { PropertyGroup group = getGroup(hierarchicalStructure, parts); if (parts.size() > 1) { PropertyGroup tmp = group; PropertyGroup root = null; for (int i = 0; i < parts.size() - 1; i++) { tmp = new PropertyGroup(tmp, parts.get(i)); if (i == 0) { root = tmp; } } p.parent = tmp; p.parent.items.add(p); if (group == null) { hierarchicalStructure.add(root); } } else { p.parent = group; p.parent.items.add(p); } } else { hierarchicalStructure.add(p); } flatStructure.add(p); } removeEmptyGroups(hierarchicalStructure); } @Override public void createControl(Composite parent) { super.createControl(parent); TreeViewer viewer = getTreeViewer(); viewer.setLabelProvider(new StyledCellLabelProvider() { @Override public void update(ViewerCell cell) { Object element = cell.getElement(); if (element instanceof PropertyGroup) { cell.setText(((PropertyGroup) element).name); cell.setImage(JavaFXUIPlugin.getDefault().getImageRegistry() .get(JavaFXUIPlugin.GROUP_ICON)); cell.setStyleRanges(null); } else if (element instanceof Property) { cell.setImage(JavaFXUIPlugin.getDefault().getImageRegistry() .get(JavaFXUIPlugin.KEY_ICON)); StyledString s = new StyledString( ((Property) element).pair.key); String text = ((Property) element).pair.value; if (text.length() > 20) { text = text.substring(0, 20) + "..."; } s.append(" : " + text, StyledString.DECORATIONS_STYLER); cell.setStyleRanges(s.getStyleRanges()); cell.setText(s.getString()); } super.update(cell); } }); viewer.setContentProvider(new HierarchicalContentProvider()); if (isSorted()) { setSorted(true); } createHierarchicalStructure(); if (isHierarchical()) { viewer.setInput(hierarchicalStructure); } else { viewer.setInput(flatStructure); } } public void setProperties(Pair[] properties) { this.properties = properties; createHierarchicalStructure(); if (getTreeViewer() != null) { getTreeViewer().refresh(); } } private boolean isSorted() { return preferences.getBoolean(PREF_SORTED, true); } private boolean isHierarchical() { return preferences.getBoolean(PREF_HIERARCHICAL, false); } private void removeEmptyGroups(List<?> list) { Iterator<?> it = list.iterator(); while (it.hasNext()) { Object o = it.next(); if (o instanceof PropertyGroup) { removeEmptyGroups(((PropertyGroup) o).groups); if (((PropertyGroup) o).groups.size() == 0 && ((PropertyGroup) o).items.size() == 0) { it.remove(); } } } } private PropertyGroup getGroup(List<?> list, List<String> parts) { PropertyGroup group = null; for (Object o : list) { if (o instanceof PropertyGroup) { if (((PropertyGroup) o).name.equals(parts.get(0))) { parts.remove(0); group = (PropertyGroup) o; if (parts.size() > 1) { PropertyGroup tmp = getGroup(group.groups, parts); if (tmp != null) { return tmp; } } return group; } } } return group; } static class LabelProvider extends ColumnLabelProvider { @Override public String getText(Object element) { return super.getText(element); } } static class HierarchicalContentProvider implements ITreeContentProvider { public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } public Object[] getElements(Object inputElement) { return ((List<?>) inputElement).toArray(); } public Object[] getChildren(Object parentElement) { if (parentElement instanceof PropertyGroup) { Object[] groups = ((PropertyGroup) parentElement).groups .toArray(); Object[] items = ((PropertyGroup) parentElement).items .toArray(); Object[] rv = new Object[groups.length + items.length]; System.arraycopy(groups, 0, rv, 0, groups.length); System.arraycopy(items, 0, rv, groups.length, items.length); return rv; } return new Object[0]; } public Object getParent(Object element) { if (element instanceof PropertyGroup) { return ((PropertyGroup) element).parent; } else if (element instanceof Property) { return ((Property) element).parent; } return null; } public boolean hasChildren(Object element) { if (element instanceof PropertyGroup) { return ((PropertyGroup) element).groups.size() > 0 || ((PropertyGroup) element).items.size() > 0; } return false; } } public static class PropertyGroup { private PropertyGroup parent; public String name; private List<PropertyGroup> groups = new ArrayList<PropertyContentOutlinePage.PropertyGroup>(); private List<Property> items = new ArrayList<Property>(); public PropertyGroup(String name) { this(null, name); } public PropertyGroup(PropertyGroup parent, String name) { this.parent = parent; if (parent != null) { parent.groups.add(this); } this.name = name; } @Override public String toString() { return super.toString() + "#" + name; } } public static class Property { private PropertyGroup parent; public Pair pair; public Property(Pair pair) { this(null, pair); } public Property(PropertyGroup parent, Pair pair) { this.parent = parent; this.pair = pair; } @Override public String toString() { return super.toString() + "#" + pair.key + " / " + pair.value; } } public static class Pair { public final String key; public final String value; public Pair(String key, String value) { this.key = key; this.value = value; } } }