/* ********************************************************************** **
** 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.filter;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.newsaction.MoveNewsAction;
import org.rssowl.core.persist.IFilterAction;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.util.LayoutUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* The {@link NewsActionList} offers the user a control to define
* {@link NewsActionItem} that represent actions to perform on news.
*
* @author bpasero
*/
public class NewsActionList extends ScrolledComposite {
private final NewsActionPresentationManager fNewsActionPresentationManager = NewsActionPresentationManager.getInstance();
private List<NewsActionItem> fItems;
private int fVisibleItemCount;
private LocalResourceManager fResources;
private Image fAddIcon;
private Image fDeleteIcon;
private Composite fContainer;
/**
* @param parent
* @param style
* @param actions the list of initial {@link IFilterAction} to show.
*/
public NewsActionList(Composite parent, int style, List<IFilterAction> actions) {
super(parent, style | SWT.V_SCROLL);
fItems = new ArrayList<NewsActionItem>();
fResources = new LocalResourceManager(JFaceResources.getResources(), this);
initResources();
initComponents(actions);
}
/**
* Sets the number of <code>NewsActionItem</code>s that should be visible in
* the List. If the number of items is higher, scrollbars will be shown
* automatically.
*
* @param count the number of <code>NewsActionItem</code>s that should be
* visible in the List.
*/
public void setVisibleItemCount(int count) {
Assert.isLegal(count >= 0);
fVisibleItemCount = count;
}
/**
* Returns <code>TRUE</code> when this List has no items with a specific
* value, and <code>FALSE</code> otherwise.
*
* @return <code>TRUE</code> when this List has no items with a specific
* value, and <code>FALSE</code> otherwise.
*/
public boolean isEmpty() {
for (NewsActionItem item : fItems) {
if (item.hasValue())
return false;
}
return true;
}
/**
* Passes focus to the first Item in the list.
*/
public void focusInput() {
if (!fItems.isEmpty())
fItems.get(0).focusInput();
}
/*
* @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
*/
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
Point point = super.computeSize(wHint, hHint, changed);
/* Compute from Action Item */
if (fVisibleItemCount > 0 && fItems.size() > 0) {
int itemHeight = fItems.get(0).computeSize(wHint, hHint).y + 4;
point.y = fVisibleItemCount * itemHeight;
}
return point;
}
/**
* @return a list of {@link IFilterAction} as defined by the user. Duplicate
* actions are automatically ignored.
*/
public List<IFilterAction> createActions() {
List<IFilterAction> actions = new ArrayList<IFilterAction>(fItems.size());
/* For each Item */
for (NewsActionItem item : fItems) {
IFilterAction action = item.createFilterAction(true);
if (action != null)
actions.add(action);
}
/* Delete Duplicate Actions */
List<IFilterAction> duplicateActions = new ArrayList<IFilterAction>(0);
for (IFilterAction action : actions) {
/* Check if already Ignored */
if (duplicateActions.contains(action))
continue;
/* Check for Actions to Ignore */
for (IFilterAction otherAction : actions) {
if (action == otherAction)
continue;
/* Same Action IDs */
if (action.getActionId().equals(otherAction.getActionId())) {
/* Ignore Action: Both Data is unspecified */
if (action.getData() == null && otherAction.getData() == null)
duplicateActions.add(otherAction);
/* Ignore Action: Both Data is identical (Case: Object) */
else if (action.getData() != null && action.getData().equals(otherAction.getData()))
duplicateActions.add(otherAction);
/* Ignore Action: Both Data is identical (Case: Arrays) */
else if (action.getData() != null && action.getData() instanceof Object[]) {
Object[] data = (Object[]) action.getData();
if (otherAction.getData() instanceof Object[]) {
Object[] otherData = (Object[]) otherAction.getData();
if (Arrays.equals(data, otherData))
duplicateActions.add(otherAction);
}
}
}
}
}
/* Remove Actions to Ignore */
actions.removeAll(duplicateActions);
return actions;
}
/**
* Shows the List of <code>IFilterAction</code> in this List.
*
* @param actions the List of <code>IFilterAction</code> to show in this List.
*/
public void showActions(List<IFilterAction> actions) {
setRedraw(false);
try {
/* Remove all */
List<NewsActionItem> itemsToRemove = new ArrayList<NewsActionItem>(fItems);
for (NewsActionItem itemToRemove : itemsToRemove) {
itemToRemove.getParent().dispose();
removeItem(itemToRemove);
}
/* Add Actions */
if (actions != null) {
boolean addDefaultAction = true;
for (IFilterAction action : actions) {
if (fNewsActionPresentationManager.hasNewsAction(action.getActionId())) {
addItem(action);
addDefaultAction = false;
}
}
if (addDefaultAction)
addItem(getDefaultAction());
}
} finally {
setRedraw(true);
}
}
private void initResources() {
fAddIcon = OwlUI.getImage(fResources, "icons/etool16/add.gif"); //$NON-NLS-1$
fDeleteIcon = OwlUI.getImage(fResources, "icons/etool16/remove.gif"); //$NON-NLS-1$
}
private void initComponents(List<IFilterAction> actions) {
/* Adjust Scrolled Composite */
setLayout(new GridLayout(1, false));
setExpandHorizontal(true);
setExpandVertical(true);
if (getVerticalBar() != null)
getVerticalBar().setIncrement(10);
/* Create the Container */
fContainer = new Composite(this, SWT.NONE);
fContainer.setLayout(LayoutUtils.createGridLayout(1, 0, 0));
fContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
setContent(fContainer);
/* Add Actions */
if (actions != null) {
for (IFilterAction action : actions)
addItem(action);
}
/* Update Size */
updateSize();
}
NewsActionItem addItem(IFilterAction action) {
return addItem(action, fItems.size());
}
NewsActionItem addItem(IFilterAction action, int index) {
return addItem(action, index, false);
}
NewsActionItem addItem(IFilterAction action, int index, boolean scroll) {
boolean wasScrollbarShowing = getVerticalBar() != null ? getVerticalBar().isVisible() : false;
/* Container for Item */
final Composite itemContainer = new Composite(fContainer, SWT.NONE);
itemContainer.setLayout(LayoutUtils.createGridLayout(2, 0, 0, 0, 0, false));
itemContainer.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
/* Create Item */
final NewsActionItem item = new NewsActionItem(itemContainer, SWT.NONE, action);
item.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
/* Create Button Box */
final ToolBar buttonBar = new ToolBar(itemContainer, SWT.FLAT);
buttonBar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
/* Button to add Action */
ToolItem addButton = new ToolItem(buttonBar, SWT.DROP_DOWN);
addButton.setImage(fAddIcon);
addButton.setToolTipText(Messages.NewsActionList_ADD_ACTION);
/* Add Menu */
final Menu actionMenu = new Menu(buttonBar);
createActionMenu(actionMenu, item);
addButton.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
if (event.detail == SWT.ARROW) {
Rectangle rect = item.getBounds();
Point pt = new Point(rect.x, rect.y + rect.height);
pt = buttonBar.toDisplay(pt);
actionMenu.setLocation(pt.x, pt.y);
actionMenu.setVisible(true);
} else
onAdd(item);
}
});
buttonBar.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
OwlUI.safeDispose(actionMenu);
}
});
/* Button to delete Action */
ToolItem deleteButton = new ToolItem(buttonBar, SWT.PUSH);
deleteButton.setImage(fDeleteIcon);
deleteButton.setToolTipText(Messages.NewsActionList_DELETE_ACTION);
deleteButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
JobRunner.runInUIThread(0, true, buttonBar, new Runnable() {
public void run() {
onDelete(item, itemContainer);
}
});
}
});
/* Add to the End */
boolean addedToEnd = false;
if (index == fItems.size()) {
addedToEnd = true;
fItems.add(item);
}
/* Add to specific Index */
else {
NewsActionItem oldItem = fItems.get(index);
fItems.add(index, item);
item.getParent().moveAbove(oldItem.getParent());
}
/* Force Layout */
layout(true, true);
update();
/* Update Size */
updateSize();
OwlUI.adjustSizeForScrollbar(getShell(), getVerticalBar(), wasScrollbarShowing);
/* Scroll to Bottom if added as last element */
if (scroll && addedToEnd)
setOrigin(0, getContent().getSize().y);
return item;
}
private void createActionMenu(Menu menu, NewsActionItem item) {
NewsActionPresentationManager manager = NewsActionPresentationManager.getInstance();
Collection<NewsActionDescriptor> actions = manager.getSortedNewsActions();
String lastSortKey = null;
for (NewsActionDescriptor action : actions) {
if (lastSortKey != null && lastSortKey.charAt(0) != action.getSortKey().charAt(0))
new MenuItem(menu, SWT.SEPARATOR);
MenuItem mItem = new MenuItem(menu, SWT.PUSH);
mItem.setText(action.getName());
hookSelectionListener(mItem, item, action.getActionId());
lastSortKey = action.getSortKey();
}
}
private void hookSelectionListener(MenuItem item, final NewsActionItem action, final String actionId) {
item.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onAdd(action, actionId);
}
});
}
void onAdd(NewsActionItem selectedItem) {
IFilterAction filterAction = createAction(selectedItem.createFilterAction(false));
addItem(filterAction, indexOf(selectedItem) + 1, true);
}
void onAdd(NewsActionItem selectedItem, String actionId) {
IFilterAction filterAction = Owl.getModelFactory().createFilterAction(actionId);
addItem(filterAction, indexOf(selectedItem) + 1, true);
}
int indexOf(NewsActionItem item) {
return fItems.indexOf(item);
}
void onDelete(final NewsActionItem item, final Composite itemContainer) {
boolean wasScrollbarShowing = getVerticalBar() != null ? getVerticalBar().isVisible() : false;
/* Delete */
itemContainer.dispose();
removeItem(item);
/* Restore Default if required */
if (fItems.size() == 0)
addItem(getDefaultAction());
OwlUI.adjustSizeForScrollbar(getShell(), getVerticalBar(), wasScrollbarShowing);
}
private IFilterAction createAction(IFilterAction current) {
IModelFactory factory = Owl.getModelFactory();
return factory.createFilterAction(current.getActionId());
}
private IFilterAction getDefaultAction() {
IModelFactory factory = Owl.getModelFactory();
return factory.createFilterAction(MoveNewsAction.ID); //TODO Layer break
}
void removeItem(NewsActionItem item) {
/* Dispose and Remove */
item.dispose();
fItems.remove(item);
/* Force Layout */
layout(true, true);
update();
/* Update Size */
updateSize();
}
private void updateSize() {
setMinSize(fContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
/**
* @return <code>true</code> if any item in the list was modified and
* <code>false</code> otherwise.
*/
public boolean isModified() {
return true; //TODO Improve
}
}