/* ****************************************************************************** * 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.properties; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.IInputSelectionProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Menu; import org.xmind.ui.util.Chainability; import org.xmind.ui.util.IChained; public class PropertiesEditor implements IInputSelectionProvider { public static final String COLOR_BACKGROUND = "org.xmind.ui.color.PropertiesEditor.background"; //$NON-NLS-1$ public static final String COLOR_CATEGORY_TITLE = "org.xmind.ui.color.PropertiesEditor.categoryTitle.foreground"; //$NON-NLS-1$ public static final String COLOR_ENTRY_FOREGROUND = "org.xmind.ui.color.PropertiesEditor.entry.foreground"; //$NON-NLS-1$ public static final String COLOR_ENTRY_SELECTED_BACKGROUND = "org.xmind.ui.color.PropertiesEditor.entry.selected.background"; //$NON-NLS-1$ public static final String COLOR_ENTRY_SELECTED_FOREGROUND = "org.xmind.ui.color.PropertiesEditor.entry.selected.foreground"; //$NON-NLS-1$ public static final String FONT_CATEGORY_TITLE = "org.xmind.ui.font.PropertiesEditor.categoryTitle"; //$NON-NLS-1$ public static final String FONT_ENTRY = "org.xmind.ui.font.PropertiesEditor.entry"; //$NON-NLS-1$ public static final String FONT_ENTRY_SELECTED = "org.xmind.ui.font.PropertiesEditor.entry.selected"; //$NON-NLS-1$ private static final class Section implements PropertyChangeListener, IChained<Section> { private final PropertiesEditor editor; private final String title; private PropertyEditingEntry firstEntry = null; private PropertyEditingEntry lastEntry = null; private PropertyEditingSection section = null; private Section prev = null; private Section next = null; public Section(PropertiesEditor editor, String title) { this.editor = editor; this.title = title; } public void create(Composite parent) { section = new PropertyEditingSection(parent); section.setTitleText(title == null ? "" : title); //$NON-NLS-1$ section.getEventSupport().addPropertyChangeListener(this); Composite client = section.getClient(); GridLayout gridLayout = new GridLayout(1, true); gridLayout.marginWidth = 5; gridLayout.marginHeight = 5; gridLayout.verticalSpacing = 3; gridLayout.horizontalSpacing = 0; client.setLayout(gridLayout); Iterator<PropertyEditingEntry> it = entries(); while (it.hasNext()) { PropertyEditingEntry entry = it.next(); entry.createControl(client); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); gridData.widthHint = SWT.DEFAULT; gridData.heightHint = SWT.DEFAULT; entry.getControl().setLayoutData(gridData); } } public void dispose() { Iterator<PropertyEditingEntry> it = entries(); while (it.hasNext()) { it.next().dispose(); } if (section != null) { section.getEventSupport().removePropertyChangeListener(this); if (section.getControl() != null && !section.getControl().isDisposed()) section.getControl().dispose(); section = null; } firstEntry = null; lastEntry = null; } public Iterator<PropertyEditingEntry> entries() { return Chainability.iterate(firstEntry, lastEntry); } public void propertyChange(java.beans.PropertyChangeEvent evt) { if (PropertyEditingSection.PROP_EXPANDED.equals(evt .getPropertyName())) { editor.reflow(); } } public Section getPrevious() { return prev; } public Section getNext() { return next; } public void setPrevious(Section element) { this.prev = element; } public void setNext(Section element) { this.next = element; } } private ScrolledComposite container = null; private IPropertySource source = null; private Map<String, Section> sectionRegistry = new HashMap<String, Section>(); private Map<String, PropertyEditingEntry> entryRegistry = new HashMap<String, PropertyEditingEntry>(); private Map<String, String> colorFontOverrides = new HashMap<String, String>(); private boolean reflowing = false; private Section firstSection = null; private Section lastSection = null; private PropertyEditingEntry selectedEntry = null; private ListenerList listeners = new ListenerList(); private Menu popupMenu = null; private IPropertyTransfer transfer = null; private List<String> filtedDescriptorIds = new ArrayList<String>(); public PropertiesEditor() { super(); } public Object getInput() { return source; } public void setInput(IPropertySource source) { IPropertySource oldSource = this.source; this.source = source; if (oldSource != source) { refresh(); } } public ISelection getSelection() { if (selectedEntry == null) return StructuredSelection.EMPTY; return new StructuredSelection(selectedEntry); } public void setSelection(ISelection selection) { } public void addSelectionChangedListener(ISelectionChangedListener listener) { listeners.add(listener); } public void removeSelectionChangedListener( ISelectionChangedListener listener) { listeners.remove(listener); } public void create(Composite parent) { Assert.isTrue(container == null); container = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL); container.setExpandHorizontal(true); container.setExpandVertical(true); container.setMinWidth(200); container.setMinHeight(40); container.addControlListener(new ControlListener() { public void controlResized(ControlEvent e) { reflow(); } public void controlMoved(ControlEvent e) { reflow(); } }); Composite contents = new Composite(container, SWT.NONE); GridLayout gridLayout = new GridLayout(1, false); gridLayout.marginWidth = 3; gridLayout.marginHeight = 3; gridLayout.verticalSpacing = 3; gridLayout.horizontalSpacing = 0; contents.setLayout(gridLayout); container.setContent(contents); initColorsFonts(); refresh(); } public void setColorFontOverrides(String id, String overridedId) { if (overridedId == null) { colorFontOverrides.remove(id); } else { colorFontOverrides.put(id, overridedId); } } private String getColorFontId(String id) { String overridedId = colorFontOverrides.get(id); if (overridedId != null) return overridedId; return id; } private Composite getContents() { return (Composite) container.getContent(); } private void initColorsFonts() { final IPropertyChangeListener colorChangeListener = new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { String colorId = event.getProperty(); if (getColorFontId(COLOR_BACKGROUND).equals(colorId)) { updateBackgroundColor(); } else if (getColorFontId(COLOR_CATEGORY_TITLE).equals(colorId)) { updateCategoryTitlesColor(); } else if (getColorFontId(COLOR_ENTRY_FOREGROUND).equals( colorId)) { updateEntriesForegroundColor(); } else if (getColorFontId(COLOR_ENTRY_SELECTED_BACKGROUND) .equals(colorId)) { updateEntriesSelectedBackgroundColor(); } else if (getColorFontId(COLOR_ENTRY_SELECTED_FOREGROUND) .equals(colorId)) { updateEntriesSelectedForegroundColor(); } } }; final IPropertyChangeListener fontChangeListener = new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { String fontId = event.getProperty(); if (getColorFontId(FONT_CATEGORY_TITLE).equals(fontId)) { updateCategoryTitlesFont(); } else if (getColorFontId(FONT_ENTRY).equals(fontId)) { updateEntriesFont(); } else if (getColorFontId(FONT_ENTRY_SELECTED).equals(fontId)) { updateEntriesSelectedFont(); } } }; JFaceResources.getColorRegistry().addListener(colorChangeListener); JFaceResources.getFontRegistry().addListener(fontChangeListener); container.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { JFaceResources.getColorRegistry().removeListener( colorChangeListener); JFaceResources.getFontRegistry().removeListener( fontChangeListener); } }); updateColorsFonts(); } protected void updateColorsFonts() { updateBackgroundColor(); updateCategoryTitlesColor(); updateCategoryTitlesFont(); updateEntriesForegroundColor(); updateEntriesFont(); updateEntriesSelectedBackgroundColor(); updateEntriesSelectedForegroundColor(); updateEntriesSelectedFont(); } private void updateBackgroundColor() { Color color = JFaceResources.getColorRegistry().get( getColorFontId(COLOR_BACKGROUND)); container.setBackground(color); getContents().setBackground(color); Iterator<Section> sections = sections(); while (sections.hasNext()) { Section section = sections.next(); if (section.section != null) section.section.setBackground(color); } } private void updateCategoryTitlesColor() { Color color = JFaceResources.getColorRegistry().get( getColorFontId(COLOR_CATEGORY_TITLE)); Iterator<Section> sections = sections(); while (sections.hasNext()) { Section section = sections.next(); if (section.section != null) { section.section.setTitleColor(color); } } } private void updateEntriesForegroundColor() { Color color = JFaceResources.getColorRegistry().get( getColorFontId(COLOR_ENTRY_FOREGROUND)); Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setForeground(color); } } private void updateCategoryTitlesFont() { Font font = JFaceResources.getFontRegistry().get( getColorFontId(FONT_CATEGORY_TITLE)); Iterator<Section> sections = sections(); while (sections.hasNext()) { Section section = sections.next(); if (section.section != null) { section.section.setTitleFont(font); } } } private void updateEntriesFont() { Font font = JFaceResources.getFontRegistry().get( getColorFontId(FONT_ENTRY)); Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setFont(font); } } private void updateEntriesSelectedBackgroundColor() { Color color = JFaceResources.getColorRegistry().get( getColorFontId(COLOR_ENTRY_SELECTED_BACKGROUND)); Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setSelectedBackground(color); } } private void updateEntriesSelectedForegroundColor() { Color color = JFaceResources.getColorRegistry().get( getColorFontId(COLOR_ENTRY_SELECTED_FOREGROUND)); Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setSelectedForeground(color); } } private void updateEntriesSelectedFont() { Font font = JFaceResources.getFontRegistry().get( getColorFontId(FONT_ENTRY_SELECTED)); Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setSelectedFont(font); } } protected Iterator<PropertyEditingEntry> entries() { return Chainability.iterate(firstSection == null ? null : firstSection.firstEntry, null); } protected Iterator<Section> sections() { return Chainability.iterate(firstSection, lastSection); } public void setFilter(List<String> filtedDescriptorIds) { this.filtedDescriptorIds = filtedDescriptorIds; } public void refresh() { if (container == null || container.isDisposed()) return; container.setRedraw(false); try { // Remove all sections and entries: Iterator<Section> sectionIt = sections(); while (sectionIt.hasNext()) { sectionIt.next().dispose(); } firstSection = null; lastSection = null; selectedEntry = null; sectionRegistry.clear(); entryRegistry.clear(); Control[] controls = getContents().getChildren(); for (int i = 0; i < controls.length; i++) { controls[i].dispose(); } if (source != null) { IPropertyDescriptor[] descs = source.getPropertyDescriptors(); for (int i = 0; i < descs.length; i++) { IPropertyDescriptor descriptor = descs[i]; if (filtedDescriptorIds != null && filtedDescriptorIds.contains(descriptor.getId())) continue; addEditingEntry(descriptor); } } createSectionControls(); updateColorsFonts(); reflow(); } finally { container.setRedraw(true); } } public void updateAll() { Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().update(); } } public void update(String propertyId) { PropertyEditingEntry entry = entryRegistry.get(propertyId); if (entry != null) { entry.update(); } } protected void addEditingEntry(IPropertyDescriptor descriptor) { PropertyEditingEntry entry = new PropertyEditingEntry(this, source, descriptor); entryRegistry.put(descriptor.getId(), entry); String category = descriptor.getCategory(); Section section = sectionRegistry.get(category); if (section == null) { section = new Section(this, category); sectionRegistry.put(category, section); if (firstSection == null || lastSection == null) { firstSection = section; } else { Chainability.insertAfter(lastSection, section); } lastSection = section; } if (section.firstEntry == null || section.lastEntry == null) { section.firstEntry = entry; if (section.getPrevious() != null) { Chainability .insertAfter(section.getPrevious().lastEntry, entry); } } else { Chainability.insertAfter(section.lastEntry, entry); } section.lastEntry = entry; } private void createSectionControls() { if (firstSection == null) return; Composite parent = getContents(); if (firstSection.getNext() == null && (firstSection.title == null || "".equals(firstSection.title))) { //$NON-NLS-1$ Iterator<PropertyEditingEntry> it = firstSection.entries(); while (it.hasNext()) { PropertyEditingEntry entry = it.next(); entry.createControl(parent); entry.getControl().setLayoutData(createSectionLayoutData()); } } else { Iterator<Section> sectionIt = sections(); while (sectionIt.hasNext()) { Section section = sectionIt.next(); section.create(parent); if (section.section != null) { section.section.getControl().setLayoutData( createSectionLayoutData()); } } } if (popupMenu != null) { Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setPopupMenu(popupMenu); } } } private GridData createSectionLayoutData() { GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); gridData.widthHint = SWT.DEFAULT; gridData.heightHint = SWT.DEFAULT; return gridData; } public Control getControl() { return container; } public void setFocus() { if (selectedEntry != null) { selectedEntry.setFocus(); } else if (firstSection != null && firstSection.firstEntry != null) { firstSection.firstEntry.setFocus(); } else if (container != null && !container.isDisposed()) { container.setFocus(); } } public void reflow() { if (container == null || container.isDisposed()) return; if (reflowing) return; reflowing = true; container.getDisplay().asyncExec(new Runnable() { public void run() { try { if (container != null && !container.isDisposed()) { container.layout(true, true); container.setMinHeight(container.getContent() .computeSize(container.getClientArea().width, SWT.DEFAULT).y); } } finally { reflowing = false; } } }); } public void select(String propertyId) { PropertyEditingEntry entry = entryRegistry.get(propertyId); if (entry != null) { select(entry); } } protected boolean select(PropertyEditingEntry entry) { if (entry == null) return false; PropertyEditingEntry oldEntry = this.selectedEntry; this.selectedEntry = entry; if (oldEntry != null) { oldEntry.setSelected(false); } entry.setSelected(true); entry.setFocus(); reveal(entry.getControl()); fireSelectionChanged(); return true; } private void reveal(Control control) { if (container != null && !container.isDisposed()) container.showControl(control); } protected void fireSelectionChanged() { ISelection selection = getSelection(); for (Object listener : listeners.getListeners()) { if (listener instanceof ISelectionChangedListener) { ((ISelectionChangedListener) listener) .selectionChanged(new SelectionChangedEvent(this, selection)); } } } public void setPopupMenu(Menu menu) { this.popupMenu = menu; Iterator<PropertyEditingEntry> entries = entries(); while (entries.hasNext()) { entries.next().setPopupMenu(menu); } if (container != null && !container.isDisposed()) { getContents().setMenu(menu); } } public void dispose() { Iterator<Section> sectionIt = sections(); while (sectionIt.hasNext()) { sectionIt.next().dispose(); } sectionRegistry.clear(); firstSection = null; lastSection = null; source = null; if (container != null && !container.isDisposed()) { container.dispose(); } container = null; } /** * @return the transfer */ public IPropertyTransfer getTransfer() { return transfer; } /** * @param transfer * the transfer to set */ public void setTransfer(IPropertyTransfer transfer) { this.transfer = transfer; } }