/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.viewers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.IColorProvider; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.xmind.ui.forms.WidgetFactory; public abstract class CategorizedViewer extends StructuredViewer { public static final Object DEFAULT_CATEGORY = new String( Messages.CategorizedViewer_UnknownCategory); private static final Object[] EMPTY_ARRAY = new Object[0]; private static final List<Object> EMPTY_LIST = Collections.emptyList(); private Object[] categories = EMPTY_ARRAY; private Map<Object, List<Object>> categorizedElements = new HashMap<Object, List<Object>>(); private Map<Object, Section> sections = new HashMap<Object, Section>(); private ScrolledForm container = null; private WidgetFactory factory; private int sectionStyle = Section.TITLE_BAR | Section.TWISTIE | Section.EXPANDED | Section.NO_TITLE_FOCUS_BOX; public int getSectionStyle() { return sectionStyle; } public void setSectionStyle(int sectionStyle) { this.sectionStyle = sectionStyle; } protected ScrolledForm getContainer() { return container; } public void createControl(Composite parent, int style) { container = createContainer(parent, style); hookControl(container); } private ScrolledForm createContainer(Composite parent, int style) { if (factory == null) factory = createWidgetFactory(parent.getDisplay()); ScrolledForm form = new ScrolledForm(parent, SWT.V_SCROLL | SWT.LEFT_TO_RIGHT); form.setExpandHorizontal(true); form.setExpandVertical(true); form.setBackground(parent.getBackground()); form.setForeground(parent.getForeground()); form.setFont(JFaceResources.getHeaderFont()); container = form; configureContainer(container); return container; } protected WidgetFactory createWidgetFactory(Display display) { return new WidgetFactory(display); } protected WidgetFactory getWidgetFactory() { return factory; } protected void configureContainer(ScrolledForm container) { container.getBody().setLayout(createFormLayout()); container.setMinWidth(1); } protected Layout createFormLayout() { GridLayout layout = new GridLayout(1, false); layout.marginWidth = 3; layout.marginHeight = 3; return layout; } protected void hookControl(Control control) { super.hookControl(control); control.addListener(SWT.Resize, new Listener() { public void handleEvent(Event event) { relayout(); } }); } private void relayout() { int width = getContainer().getClientArea().width; for (Section section : sections.values()) { resetWidth(width, section); } container.reflow(true); } private void resetWidth(int width, Control control) { Object ld = control.getLayoutData(); if (ld instanceof GridData) { GridData gd = (GridData) ld; GridLayout gl = (GridLayout) control.getParent().getLayout(); gd.widthHint = width - gl.marginWidth * 2 - gl.marginLeft - gl.marginRight; } else if (ld instanceof RowData) { RowData rd = (RowData) ld; RowLayout rl = (RowLayout) control.getParent().getLayout(); rd.width = width - rl.marginWidth * 2 - rl.marginLeft - rl.marginRight; } } protected List<Object> getCategories() { return Arrays.asList(categories); } protected List<Object> getElements(Object category) { List<Object> elements = categorizedElements.get(category); if (elements == null) return EMPTY_LIST; return Collections.unmodifiableList(elements); } protected boolean hasCategory(Object category) { return categorizedElements.containsKey(category); } protected Composite getSection(Object category) { return sections.get(category); } protected Widget doFindInputItem(Object element) { return getControl(); } protected Widget doFindItem(Object element) { if (hasCategory(element)) return getSection(element); return getControl(); } protected void inputChanged(Object input, Object oldInput) { super.inputChanged(input, oldInput); refresh(true); } protected void internalRefresh(Object element) { internalRefresh(element, true); } protected void internalRefresh(Object element, boolean updateLabels) { if (element == getInput()) { Map<Object, Boolean> expansionStates = new HashMap<Object, Boolean>(); for (Object category : getCategories()) { Boolean exp = getExpanded(category); if (exp != null) { expansionStates.put(category, exp); } } rebuildMap(); refreshSections(); for (Object category : getCategories()) { Boolean exp = expansionStates.get(category); if (exp != null) { setExpanded(category, exp.booleanValue()); } } if (getContainer() != null && !getContainer().isDisposed()) { relayout(); } } else if (hasCategory(element)) { rebuildMapForCategory(element); if (updateLabels) updateSection(element, getSection(element)); refreshSectionContent(getSectionContent(element), element, null); } else { Object category = getCategory(element); if (category != null && hasCategory(category)) { refreshSectionContent(getSectionContent(category), category, element); } } } public Boolean getExpanded(Object category) { Composite section = getSection(category); if (section instanceof Section) { return Boolean.valueOf(((Section) section).isExpanded()); } return null; } public void setExpanded(Object category, boolean expanded) { Composite section = getSection(category); if (section instanceof Section) { ((Section) section).setExpanded(expanded); } } public void setAllExpanded(boolean expanded) { for (Section section : sections.values()) { section.setExpanded(expanded); } } private void rebuildMap() { categorizedElements.clear(); if (getContentProvider() instanceof ITreeContentProvider) { categories = getSortedChildren(getInput()); for (int i = 0; i < categories.length; i++) { Object category = categories[i]; Object[] children = ((ITreeContentProvider) getContentProvider()) .getChildren(category); ViewerFilter[] filters = getFilters(); if (filters != null && filters.length > 0) { for (ViewerFilter f : filters) { Object[] filteredChildren = f.filter(this, getInput(), children); if (filteredChildren != null) children = filteredChildren; } } if (getSorter() != null) { getSorter().sort(this, children); } categorizedElements.put(category, Arrays.asList(children)); } } else if (getContentProvider() instanceof ICategorizedContentProvider) { Object[] elements = getSortedChildren(getInput()); List<Object> rawCategories = new ArrayList<Object>(elements.length); for (Object element : elements) { Object category = getCategory(element); List<Object> list = categorizedElements.get(category); if (list == null) { list = new ArrayList<Object>(elements.length); categorizedElements.put(category, list); rawCategories.add(category); } list.add(element); } categories = rawCategories.toArray(); ViewerFilter[] filters = getFilters(); if (filters != null && filters.length > 0) { for (ViewerFilter f : filters) { Object[] filteredCategories = f.filter(this, getInput(), categories); if (filteredCategories != null) categories = filteredCategories; } } if (getSorter() != null) { getSorter().sort(this, categories); } } } private void rebuildMapForCategory(Object category) { if (getContentProvider() instanceof ITreeContentProvider) { Object[] children = ((ITreeContentProvider) getContentProvider()) .getChildren(category); ViewerFilter[] filters = getFilters(); if (filters != null && filters.length > 0) { for (ViewerFilter f : filters) { Object[] filteredChildren = f.filter(this, getInput(), children); if (filteredChildren != null) children = filteredChildren; } } if (getSorter() != null) { getSorter().sort(this, children); } categorizedElements.put(category, Arrays.asList(children)); } else if (getContentProvider() instanceof ICategorizedContentProvider) { Object[] elements = getSortedChildren(getInput()); List<Object> elementsInCategory = new ArrayList<Object>( elements.length); for (Object element : elements) { if (getCategory(element).equals(category)) { elementsInCategory.add(element); } } categorizedElements.put(category, elementsInCategory); } } protected Object getCategory(Object element) { Object category = null; if (getContentProvider() instanceof ICategorizedContentProvider) { category = ((ICategorizedContentProvider) getContentProvider()) .getCategory(element); } else if (getContentProvider() instanceof ITreeContentProvider) { category = ((ITreeContentProvider) getContentProvider()) .getParent(element); } if (category != null) return category; return DEFAULT_CATEGORY; } private void refreshSections() { if (getContainer() == null || getContainer().isDisposed()) return; getContainer().setRedraw(false); try { Set<Section> sectionsToRemove = new HashSet<Section>( sections.values()); Section lastSection = null; Composite parent = container.getBody(); for (int i = 0; i < categories.length; i++) { Object category = categories[i]; Section section = sections.get(category); if (section == null) { section = createSection(parent, category); sections.put(category, section); } updateSection(category, section); refreshSectionContent(section.getClient(), category, null); section.moveBelow(lastSection); lastSection = section; sectionsToRemove.remove(section); } for (Section section : sectionsToRemove) { disposeSection(section); } } finally { if (getContainer() != null && !getContainer().isDisposed()) { getContainer().setRedraw(true); } } } private void disposeSection(Section section) { Object category = section.getData(); sections.remove(category); disposeSectionContent(section, category); section.setMenu(null); section.dispose(); } private Section createSection(Composite parent, Object category) { Section section = factory.createSection(parent, getSectionStyle()); section.setData(category); section.setLayoutData(getSectionLayoutData(section, category)); Control content = createSectionContent(section, category); Assert.isNotNull(content); section.setClient(content); configureSection(section, category); return section; } protected void configureSection(Section section, Object category) { section.setTitleBarBackground( section.getDisplay().getSystemColor(SWT.COLOR_WHITE)); section.setTitleBarBorderColor( section.getDisplay().getSystemColor(SWT.COLOR_WHITE)); } protected Object getSectionLayoutData(Section section, Object category) { return new GridData(SWT.FILL, SWT.FILL, true, false); } private Control getSectionContent(Object category) { Composite section = getSection(category); if (section instanceof Section && !section.isDisposed()) return ((Section) section).getClient(); return null; } protected String getText(Object element) { if (getLabelProvider() instanceof ILabelProvider) { return ((ILabelProvider) getLabelProvider()).getText(element); } return element == null ? null : element.toString(); } protected Image getImage(Object element) { if (getLabelProvider() instanceof ILabelProvider) { return ((ILabelProvider) getLabelProvider()).getImage(element); } return null; } protected Color getForeground(Object element) { if (getLabelProvider() instanceof IColorProvider) { return ((IColorProvider) getLabelProvider()).getForeground(element); } return null; } protected Color getBackground(Object element) { if (getLabelProvider() instanceof IColorProvider) { return ((IColorProvider) getLabelProvider()).getBackground(element); } return null; } protected void doUpdateItem(Widget item, Object element, boolean fullMap) { if (hasCategory(element)) { updateSection(element, getSection(element)); } } protected void updateSection(Object category, Composite section) { section.setData(category); if (section instanceof Section) { Section s = (Section) section; String text = getText(category); if (text != null) s.setText(text); Color foreground = getForeground(category); if (foreground != null) s.setTitleBarForeground(foreground); Color background = getBackground(category); if (background != null) s.setTitleBarBackground(background); } } public void reveal(Object element) { if (hasCategory(element)) { reveal(element, null); } else { Object category = getCategory(element); reveal(category, element); } } protected void reveal(Object category, Object element) { Composite section = getSection(category); if (section != null && !section.isDisposed()) { if (section instanceof Section) { ((Section) section).setExpanded(true); } Point loc = Display.getCurrent().map(section, getContainer(), 0, 0); reveal(loc.x, loc.y); } } protected void reveal(int x, int y) { Point origin = container.getOrigin(); origin.x += x; origin.y += y; container.setOrigin(origin); } public Control getControl() { return container; } protected List getSelectionFromWidget() { List list = new ArrayList(); for (Object category : getCategories()) { fillSelection(category, list); } return list; } protected void setSelectionToWidget(List l, boolean reveal) { } protected void setSelectionToWidget(ISelection selection, boolean reveal) { for (Object category : getCategories()) { setSelectionToCategory(category, selection, reveal); } if (reveal) { Object element = ((IStructuredSelection) getSelection()) .getFirstElement(); if (element != null) { reveal(element); } } } protected void handleDispose(DisposeEvent event) { super.handleDispose(event); sections.clear(); categories = EMPTY_ARRAY; categorizedElements.clear(); if (factory != null) { factory.dispose(); factory = null; } } public void setFocus() { for (Object category : getCategories()) { Composite section = getSection(category); if (section instanceof Section) { if (((Section) section).getClient().setFocus()) return; } } } protected abstract Control createSectionContent(Composite parent, Object category); protected abstract void disposeSectionContent(Composite section, Object category); protected abstract void refreshSectionContent(Control content, Object category, Object element); protected abstract void fillSelection(Object category, List selection); protected abstract void setSelectionToCategory(Object category, ISelection selection, boolean reveal); }