/* ********************************************************************** ** ** Copyright notice ** ** ** ** (c) 2005-2009 RSSOwl Development Team ** ** http://www.rssowl.org/ ** ** ** ** 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.rssowl.org/legal/epl-v10.html ** ** ** ** A copy is found in the file epl-v10.html and important notices to the ** ** license from the team is found in the textfile LICENSE.txt distributed ** ** in this package. ** ** ** ** This copyright notice MUST APPEAR in all copies of the file! ** ** ** ** Contributors: ** ** RSSOwl Development Team - initial API and implementation ** ** ** ** ********************************************************************** */ package org.rssowl.ui.internal.util; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; 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.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.rssowl.core.Owl; import org.rssowl.core.internal.persist.pref.DefaultPreferences; import org.rssowl.core.persist.IFolder; import org.rssowl.core.persist.dao.DynamicDAO; import org.rssowl.core.persist.event.FolderAdapter; import org.rssowl.core.persist.event.FolderEvent; import org.rssowl.core.util.CoreUtils; import org.rssowl.ui.internal.ApplicationWorkbenchWindowAdvisor; import org.rssowl.ui.internal.OwlUI; import org.rssowl.ui.internal.actions.NewFolderAction; import org.rssowl.ui.internal.views.explorer.BookMarkLabelProvider; import java.util.Collection; import java.util.List; import java.util.Set; /** * The <code>FolderChooser</code> allows to select a <code>IFolder</code>. It * provides an expandable Tree-Viewer to display all folders which is initially * collapsed. * * @author bpasero */ public class FolderChooser extends Composite implements DisposeListener { /* Delay in ms before updating Selection on Events */ private static final int SELECTION_DELAY = 20; /* Default Height in Items */ private static final int DEFAULT_ITEM_HEIGHT = 10; private Composite fParent; private IFolder fSelectedFolder; private ResourceManager fResources; private Composite fFolderViewerContainer; private ToolItem fToggleItem; private TreeViewer fFolderViewer; private Label fFolderIcon; private Label fFolderName; private int fViewerHeight; private FolderAdapter fFolderListener; private ToolBar fAddFolderBar; private List<IFolder> fExcludes; private final boolean fExpandable; private int fItemHeight; private ExpandStrategy fExpandStrategy = ExpandStrategy.RESIZE; /** The strategy when expanding the Control */ public enum ExpandStrategy { /** Pack the Parent */ PACK, /** Resize the Parent */ RESIZE; } /** * @param parent * @param initial * @param style * @param expandable */ public FolderChooser(Composite parent, IFolder initial, int style, boolean expandable) { this(parent, initial, null, style, expandable); } /** * @param parent * @param initial * @param excludes * @param style * @param expandable */ public FolderChooser(Composite parent, IFolder initial, List<IFolder> excludes, int style, boolean expandable) { this(parent, initial, excludes, style, expandable, DEFAULT_ITEM_HEIGHT); } /** * @param parent * @param initial * @param excludes * @param style * @param expandable * @param itemHeight */ public FolderChooser(Composite parent, IFolder initial, List<IFolder> excludes, int style, boolean expandable, int itemHeight) { super(parent, style); fParent = parent; fSelectedFolder = initial; fExcludes = excludes; fExpandable = expandable; fItemHeight = itemHeight; fResources = new LocalResourceManager(JFaceResources.getResources(), parent); initComponents(); addDisposeListener(this); } /** * @return Returns the <code>IFolder</code> that has been selected. */ public IFolder getFolder() { return fSelectedFolder; } /** * @param expandStrategy the strategy when expanding the Control (either * {@link ExpandStrategy#PACK} or {@link ExpandStrategy#RESIZE}. */ public void setExpandStrategy(ExpandStrategy expandStrategy) { fExpandStrategy = expandStrategy; } /* * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) */ public void widgetDisposed(DisposeEvent e) { unregisterListeners(); } private void registerListeners() { fFolderListener = new FolderAdapter() { @Override public void entitiesUpdated(final Set<FolderEvent> events) { if (events.isEmpty()) return; /* Refresh and show added Folder */ JobRunner.runInUIThread(fFolderViewer.getControl(), new Runnable() { public void run() { fFolderViewer.refresh(); FolderEvent event = events.iterator().next(); expand(event.getEntity()); } }); } @Override public void entitiesAdded(final Set<FolderEvent> events) { if (events.isEmpty()) return; /* Select added Folder */ JobRunner.runInUIThread(SELECTION_DELAY, fFolderViewer.getControl(), new Runnable() { public void run() { FolderEvent event = events.iterator().next(); fFolderViewer.setSelection(new StructuredSelection(event.getEntity())); } }); } }; DynamicDAO.addEntityListener(IFolder.class, fFolderListener); } private void expand(IFolder folder) { IFolder parent = folder.getParent(); if (parent != null) expand(parent); fFolderViewer.setExpandedState(folder, true); } private void unregisterListeners() { DynamicDAO.removeEntityListener(IFolder.class, fFolderListener); } private void initComponents() { Composite headerContainer = new Composite(this, SWT.None); headerContainer.setLayout(LayoutUtils.createGridLayout(3, 0, 0)); ((GridLayout) headerContainer.getLayout()).marginLeft = 3; headerContainer.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); headerContainer.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); if (fExpandable) { headerContainer.setCursor(fParent.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); headerContainer.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { onToggle(); notifyListeners(SWT.Selection, new Event()); } }); } fFolderIcon = new Label(headerContainer, SWT.None); fFolderIcon.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, true)); fFolderIcon.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); if (fExpandable) { fFolderIcon.setCursor(fParent.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); fFolderIcon.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { onToggle(); notifyListeners(SWT.Selection, new Event()); } }); } fFolderName = new Label(headerContainer, SWT.None); fFolderName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); fFolderName.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); if (fExpandable) { fFolderName.setCursor(fParent.getDisplay().getSystemCursor(SWT.CURSOR_HAND)); fFolderName.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { onToggle(); notifyListeners(SWT.Selection, new Event()); } }); } Composite toolbarContainer = new Composite(headerContainer, SWT.NONE); toolbarContainer.setLayout(LayoutUtils.createGridLayout(2, 0, 0, 0, 1, false)); toolbarContainer.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, true)); toolbarContainer.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); fAddFolderBar = new ToolBar(toolbarContainer, SWT.FLAT); OwlUI.makeAccessible(fAddFolderBar, Messages.FolderChooser_NEW_FOLDER); fAddFolderBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, true)); fAddFolderBar.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); fAddFolderBar.setCursor(headerContainer.getDisplay().getSystemCursor(SWT.CURSOR_ARROW)); fAddFolderBar.setVisible(!fExpandable); ToolItem addFolderItem = new ToolItem(fAddFolderBar, SWT.PUSH); addFolderItem.setImage(OwlUI.getImage(fResources, "icons/etool16/add_crop.gif")); //$NON-NLS-1$ addFolderItem.setToolTipText(Messages.FolderChooser_NEW_FOLDER); addFolderItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onNewFolder(); notifyListeners(SWT.Selection, new Event()); } }); ToolBar toggleBar = new ToolBar(toolbarContainer, SWT.FLAT); OwlUI.makeAccessible(toggleBar, Messages.FolderChooser_SHOW_FOLDERS); toggleBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, true)); ((GridData) toggleBar.getLayoutData()).exclude = !fExpandable; toggleBar.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); toggleBar.setCursor(headerContainer.getDisplay().getSystemCursor(SWT.CURSOR_ARROW)); fToggleItem = new ToolItem(toggleBar, SWT.PUSH); fToggleItem.setImage(OwlUI.getImage(fResources, "icons/ovr16/arrow_down.gif")); //$NON-NLS-1$ fToggleItem.setToolTipText(Messages.FolderChooser_SHOW_FOLDERS); fToggleItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onToggle(); notifyListeners(SWT.Selection, new Event()); } }); fFolderViewerContainer = new Composite(this, SWT.None); fFolderViewerContainer.setLayout(LayoutUtils.createGridLayout(1, 0, 0, 2, 0, false)); fFolderViewerContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); fFolderViewerContainer.setBackground(fParent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); Label separator = new Label(fFolderViewerContainer, SWT.SEPARATOR | SWT.HORIZONTAL); separator.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); fFolderViewer = new TreeViewer(fFolderViewerContainer, SWT.None); fFolderViewer.setAutoExpandLevel(2); fFolderViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); fFolderViewer.getTree().setData(ApplicationWorkbenchWindowAdvisor.FOCUSLESS_SCROLL_HOOK, new Object()); fViewerHeight = fFolderViewer.getTree().getItemHeight() * fItemHeight + 12; ((GridData) fFolderViewerContainer.getLayoutData()).heightHint = fViewerHeight; ((GridData) fFolderViewerContainer.getLayoutData()).exclude = fExpandable; /* Sort by Name if set so */ if (Owl.getPreferenceService().getGlobalScope().getBoolean(DefaultPreferences.BE_SORT_BY_NAME)) { fFolderViewer.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { IFolder f1 = (IFolder) e1; IFolder f2 = (IFolder) e2; return f1.getName().compareTo(f2.getName()); } }); } /* Filter excluded Folders */ fFolderViewer.addFilter(new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { if (fExcludes == null) return true; return !fExcludes.contains(element) && !fExcludes.contains(parentElement); } }); fFolderViewer.setContentProvider(new ITreeContentProvider() { public Object[] getElements(Object inputElement) { Collection<IFolder> rootFolders = CoreUtils.loadRootFolders(); return rootFolders.toArray(); } public Object[] getChildren(Object parentElement) { if (parentElement instanceof IFolder) { IFolder folder = (IFolder) parentElement; return folder.getFolders().toArray(); } return new Object[0]; } public Object getParent(Object element) { if (element instanceof IFolder) { IFolder folder = (IFolder) element; return folder.getParent(); } return null; } public boolean hasChildren(Object element) { if (element instanceof IFolder) { IFolder folder = (IFolder) element; return !folder.getFolders().isEmpty(); } return false; } public void dispose() {} public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {} }); boolean usedInDialog = (fParent.getShell().getParent() != null); fFolderViewer.setLabelProvider(new BookMarkLabelProvider(false, usedInDialog)); fFolderViewer.setInput(new Object()); fFolderViewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); if (!selection.isEmpty()) onFolderSelected((IFolder) selection.getFirstElement()); notifyListeners(SWT.Selection, new Event()); } }); fFolderViewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); IFolder folder = (IFolder) selection.getFirstElement(); /* Expand / Collapse Folder */ if (!folder.getFolders().isEmpty()) { boolean expandedState = !fFolderViewer.getExpandedState(folder); fFolderViewer.setExpandedState(folder, expandedState); } /* Select Folder and toggle */ else if (fExpandable) { onToggle(); } } }); /* Select the input Folder and expand */ fFolderViewer.setSelection(new StructuredSelection(fSelectedFolder)); fFolderViewer.setExpandedState(fSelectedFolder, true); /* Add Menu: "New Folder" */ MenuManager menuManager = new MenuManager(); menuManager.add(new Action(Messages.FolderChooser_NEW_FOLDER) { @Override public void run() { onNewFolder(); } }); Menu menu = menuManager.createContextMenu(fFolderViewer.getTree()); fFolderViewer.getTree().setMenu(menu); /* Register Model Listeners */ registerListeners(); } private void onNewFolder() { /* Make sure Folder-List is visible */ if (((GridData) fFolderViewerContainer.getLayoutData()).exclude && fExpandable) onToggle(); /* Create new Folder */ IStructuredSelection selection = (IStructuredSelection) fFolderViewer.getSelection(); NewFolderAction action = new NewFolderAction(fFolderViewer.getTree().getShell(), (IFolder) selection.getFirstElement(), null); action.run(null); } private void onFolderSelected(IFolder folder) { fSelectedFolder = folder; fFolderIcon.setImage(OwlUI.getImage(fResources, folder.getParent() != null ? OwlUI.FOLDER : OwlUI.BOOKMARK_SET)); fFolderName.setText(folder.getName()); } private void onToggle() { boolean excluded = ((GridData) fFolderViewerContainer.getLayoutData()).exclude; fToggleItem.setImage(OwlUI.getImage(fResources, excluded ? "icons/ovr16/arrow_up.gif" : "icons/ovr16/arrow_down.gif")); //$NON-NLS-1$ //$NON-NLS-2$ fToggleItem.setToolTipText(excluded ? Messages.FolderChooser_HIDE_FOLDERS : Messages.FolderChooser_SHOW_FOLDERS); ((GridData) fFolderViewerContainer.getLayoutData()).exclude = !excluded; Shell shell = fFolderViewerContainer.getShell(); shell.layout(); fAddFolderBar.setVisible(excluded); /* Increase Size of Shell to fit Control */ if (fExpandStrategy == ExpandStrategy.RESIZE) { Point size = shell.getSize(); shell.setSize(size.x, size.y + (excluded ? fViewerHeight : -fViewerHeight)); } /* Pack Shell and expect enough room to fit the Control */ else { int currentWidth = shell.getSize().x; Point desiredSize = shell.computeSize(currentWidth, SWT.DEFAULT); if (desiredSize.y > shell.getSize().y) shell.setSize(currentWidth, desiredSize.y); else shell.layout(true, true); } if (excluded) fFolderViewer.getTree().setFocus(); else fFolderViewer.getTree().getShell().setFocus(); } }