/******************************************************************************* * Copyright (c) 2011, 2013, 2014 Torkild U. Resheim. * * 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: * Torkild U. Resheim - initial API and implementation *******************************************************************************/ package no.resheim.elibrarium.epub.ui.reader; import no.resheim.elibrarium.epub.core.EpubUtil; import no.resheim.elibrarium.epub.ui.EpubUiPlugin; import no.resheim.elibrarium.library.Book; import no.resheim.elibrarium.library.Bookmark; import no.resheim.elibrarium.library.TextAnnotation; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.preference.JFacePreferences; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.OwnerDrawLabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.mylyn.docs.epub.core.Publication; import org.eclipse.mylyn.docs.epub.ncx.NavPoint; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Region; import org.eclipse.swt.layout.GridData; 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.Menu; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.part.Page; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.eclipse.ui.themes.ITheme; import org.eclipse.ui.themes.IThemeManager; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.ocpsoft.pretty.time.PrettyTime; /** * Use to provide an outline page for EPUB table of contents. * * @author Torkild U. Resheim */ public class TOCOutlinePage extends Page implements IContentOutlinePage, ISelectionChangedListener, IDoubleClickListener, IPropertyChangeListener { private static final String TITLE_FONT = "no.resheim.elibrarium.epub.ui.titleFont"; private static final String DATE_FONT = "no.resheim.elibrarium.epub.ui.dateFont"; /** * Use to sort bookmarks by page numbers. The bookmark with the lowest * number will come first. */ private final class BookmarksComparator extends ViewerComparator { @Override public int compare(Viewer viewer, Object e1, Object e2) { if (e1 instanceof Bookmark && e2 instanceof Bookmark) { int page_e1 = ((Bookmark) e1).getPage(); int page_e2 = ((Bookmark) e2).getPage(); return page_e1 - page_e2; } return super.compare(viewer, e1, e2); } } /** * Lists bookmarks */ private final class BookmarksContentProvider implements IStructuredContentProvider { public void dispose() { } public Object[] getElements(Object parent) { if (parent instanceof Book) { Book book = (Book) parent; return book.getBookmarks().toArray(); } return new Object[0]; } public void inputChanged(Viewer v, Object oldInput, Object newInput) { } } private StackLayout layout; private TableViewer bookmarks; private final Publication ops; private Composite pagebook; private final EpubReader reader; private final ListenerList selectionChangedListeners = new ListenerList(); private IAction showAnnotations; private IAction showTOC; private TreeViewer toc; public TOCOutlinePage(Publication epub, EpubReader reader) { this.ops = epub; this.reader = reader; } public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.add(listener); } @Override public void dispose() { // No longer listen to events from this book book.eAdapters().remove(bookAdapter); super.dispose(); } @Override public void createControl(Composite parent) { book = EpubUtil.getBook(ops); pagebook = new Composite(parent, SWT.NONE); layout = new StackLayout(); pagebook.setLayout(layout); toc = new TreeViewer(pagebook, getTreeStyle()); toc.setContentProvider(new TOCContentProvider()); toc.setLabelProvider(new EpubLabelProvider()); toc.addSelectionChangedListener(this); toc.addDoubleClickListener(this); bookmarks = new TableViewer(pagebook, SWT.NONE); bookmarks.setContentProvider(new BookmarksContentProvider()); bookmarks.setComparator(new BookmarksComparator()); bookmarks.addSelectionChangedListener(this); bookmarks.addDoubleClickListener(this); bookmarks.getTable().setHeaderVisible(false); final TableViewerColumn column = new TableViewerColumn(bookmarks, SWT.LEFT); // Special drawing of bookmarks table installLabelProvider(column); // Automatic layout of bookmarks table installControlAdapter(column); GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.FILL_BOTH); bookmarks.getControl().setLayoutData(data); // Create the context menu for the annotations hookContextMenu(bookmarks.getControl()); // Set the input toc.setInput(ops); toc.expandAll(); bookmarks.setInput(book); // Handles changes in the book and will refresh the bookmarks view. bookAdapter = new AdapterImpl() { @Override public void notifyChanged(Notification notification) { if (!bookmarks.getControl().isDisposed()) { bookmarks.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { bookmarks.refresh(true); } }); } } }; book.eAdapters().add(bookAdapter); // Start by showing the table of contents showTOC.setChecked(true); layout.topControl = toc.getControl(); pagebook.layout(); JFaceResources.getFontRegistry().addListener(this); // Activate UI context for o.e.u.meny contributes items activateContext(); } private void installControlAdapter(final TableViewerColumn column) { // Automatically adjust column and table sizes pagebook.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { org.eclipse.swt.graphics.Rectangle area = pagebook.getClientArea(); Point preferredSize = bookmarks.getTable().computeSize(SWT.DEFAULT, SWT.DEFAULT); int width = area.width - 2 * bookmarks.getTable().getBorderWidth(); if (preferredSize.y > area.height + bookmarks.getTable().getHeaderHeight()) { // Subtract the scrollbar width from the total column width // if a vertical scrollbar will be required Point vBarSize = bookmarks.getTable().getVerticalBar().getSize(); width -= vBarSize.x; } Point oldSize = bookmarks.getTable().getSize(); if (oldSize.x > area.width) { // table is getting smaller so make the columns // smaller first and then resize the table to // match the client area width column.getColumn().setWidth(width); bookmarks.getTable().setSize(area.width, area.height); } else { // table is getting bigger so make the table // bigger first and then make the columns wider // to match the client area width column.getColumn().setWidth(width); bookmarks.getTable().setSize(area.width, area.height); } } }); } private void installLabelProvider(final TableViewerColumn column) { final PrettyTime pt = new PrettyTime(); column.setLabelProvider(new OwnerDrawLabelProvider() { @Override protected void paint(Event event, Object element) { GC gc = event.gc; Display display = column.getViewer().getControl().getDisplay(); Bookmark bookmark = (Bookmark) element; String date = pt.format(bookmark.getTimestamp()); int width = column.getColumn().getWidth(); // Calculate the size of the date string Point size = event.gc.textExtent(date, SWT.DRAW_DELIMITER | SWT.DRAW_TAB); int halfHeight = size.y / 2; int height = size.y; // if ((event.detail & SWT.SELECTED) != 0) { // Region region = new Region(); // gc.getClipping(region); // region.translate(10, 10); // // region.add(event.x, event.y - halfHeight, width, // // event.height - halfHeight); // gc.setClipping(region); // region.dispose(); // // Rectangle rect = event.getBounds(); // Color foreground = gc.getForeground(); // Color background = gc.getBackground(); // gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION)); // gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); // gc.fillGradientRectangle(0, rect.y, 500, rect.height, false); // // restore colors for subsequent drawing // gc.setForeground(foreground); // gc.setBackground(background); // event.detail &= ~SWT.SELECTED; // } // Draw the date gc.setFont(getFont(DATE_FONT)); gc.setForeground(JFaceResources.getColorRegistry().get(JFacePreferences.QUALIFIER_COLOR)); gc.drawText(date, width - size.x, event.y + size.y, true); // Paint the page number of the bookmark int pageNumber = bookmark.getPage(); String page = Integer.toString(pageNumber); Point pageSize = event.gc.textExtent(page, SWT.DRAW_DELIMITER | SWT.DRAW_TAB); gc.drawText(page, width - 16 - pageSize.x, event.y, true); // Draw icon if (bookmark instanceof TextAnnotation) { } else { int x = width - 9; gc.drawImage(EpubUiPlugin.getDefault().getImageRegistry().get(EpubUiPlugin.IMG_BOOKMARK), x, event.y + 1); } // Draw title gc.setFont(getFont(TITLE_FONT)); gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); // Make sure text does not span to far Region region = new Region(); gc.getClipping(region); region.subtract(width - 32 - pageSize.x, event.y, width, height); gc.setClipping(region); region.dispose(); String text = bookmark.getText(); if (text == null) { text = "<missing text>"; } if (bookmark instanceof TextAnnotation) { if ((event.detail & SWT.SELECTED) == 0) { gc.setBackground(display.getSystemColor(SWT.COLOR_YELLOW)); gc.drawText(text, event.x + 1, event.y, false); } else { gc.drawText(text, event.x + 1, event.y, true); } } else { gc.drawText(text, event.x + 1, event.y, true); } // Draw separator drawUnderline(event, gc, width, size, halfHeight); } public Font getFont(String fontName) { IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager(); ITheme currentTheme = themeManager.getCurrentTheme(); FontRegistry fontRegistry = currentTheme.getFontRegistry(); Font font = fontRegistry.get(fontName); return font; } /** * Draws a separator between the rows. */ public void drawUnderline(Event event, GC gc, int width, Point size, int halfHeight) { int y = (event.y + size.y * 2) + halfHeight - 2; int center = (width / 2); gc.setForeground(JFaceResources.getColorRegistry().get(JFacePreferences.QUALIFIER_COLOR)); gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); gc.fillGradientRectangle(center, y, center, 1, false); gc.setBackground(JFaceResources.getColorRegistry().get(JFacePreferences.QUALIFIER_COLOR)); gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); gc.fillGradientRectangle(0, y, center, 1, false); } @Override protected void measure(Event event, Object element) { String text = "A"; Point size = event.gc.textExtent(text, SWT.DRAW_DELIMITER | SWT.DRAW_TAB); event.width = bookmarks.getTable().getColumn(event.index).getWidth(); // we need two lines of text and some space int halfHeight = size.y / 2; event.height = size.y * 2 + halfHeight; } }); } @Override public void doubleClick(DoubleClickEvent event) { IStructuredSelection s = (IStructuredSelection) event.getSelection(); Object element = s.getFirstElement(); if (element instanceof NavPoint) { reader.navigateTo((NavPoint) element); } else if (element instanceof TextAnnotation) { reader.navigateTo((TextAnnotation) element); } else if (element instanceof Bookmark) { reader.navigateTo((Bookmark) element); } } private void fillContextMenu(IMenuManager manager) { // Other plug-ins can contribute there actions here manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } /** * Fires a selection changed event. * * @param selection * the new selection */ protected void fireSelectionChanged(ISelection selection) { // create an event final SelectionChangedEvent event = new SelectionChangedEvent(this, selection); // fire the event Object[] listeners = selectionChangedListeners.getListeners(); for (Object listener : listeners) { final ISelectionChangedListener l = (ISelectionChangedListener) listener; SafeRunner.run(new SafeRunnable() { public void run() { l.selectionChanged(event); } }); } } /* * (non-Javadoc) Method declared on IPage (and Page). */ @Override public Control getControl() { if (pagebook == null) { return null; } return pagebook; } /* * (non-Javadoc) Method declared on ISelectionProvider. */ public ISelection getSelection() { if (toc == null) { return StructuredSelection.EMPTY; } return toc.getSelection(); } /** * A hint for the styles to use while constructing the TreeViewer. * <p> * Subclasses may override. * </p> * * @return the tree styles to use. By default, SWT.MULTI | SWT.H_SCROLL | * SWT.V_SCROLL * @since 3.6 */ protected int getTreeStyle() { return SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL; } /** * Returns this page's tree viewer. * * @return this page's tree viewer, or <code>null</code> if * <code>createControl</code> has not been called yet */ protected TreeViewer getTreeViewer() { return toc; } private void hookContextMenu(Control control) { MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { TOCOutlinePage.this.fillContextMenu(manager); } }); Menu menu = menuMgr.createContextMenu(bookmarks.getControl()); control.setMenu(menu); getSite().registerContextMenu("no.resheim.elibrarium.bookmarks", menuMgr, bookmarks); } @Override public void init(IPageSite pageSite) { super.init(pageSite); pageSite.setSelectionProvider(this); makeActions(); IToolBarManager toolbar = getSite().getActionBars().getToolBarManager(); toolbar.add(showTOC); toolbar.add(showAnnotations); } private void makeActions() { showTOC = new Action("Contents", IAction.AS_RADIO_BUTTON) { @Override public void run() { layout.topControl = toc.getControl(); pagebook.layout(); } }; showAnnotations = new Action("Notes", IAction.AS_RADIO_BUTTON) { @Override public void run() { layout.topControl = bookmarks.getControl(); pagebook.layout(); } }; showTOC.setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(EpubUiPlugin.PLUGIN_ID, "icons/contents.gif")); showAnnotations.setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(EpubUiPlugin.PLUGIN_ID, "icons/marking.gif")); } public void removeSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.remove(listener); } public void selectionChanged(SelectionChangedEvent event) { fireSelectionChanged(event.getSelection()); } /** * Sets focus to a part in the page. */ @Override public void setFocus() { toc.getControl().setFocus(); } public void setSelection(ISelection selection) { if (toc != null) { toc.setSelection(selection); } } private static final String VIEW_CONTEXT_ID = "no.resheim.elibrarium.bookmarks"; //$NON-NLS-1$ private Adapter bookAdapter; private Book book; /** * Activate a context that this view uses. It will be tied to this view * activation events and will be removed when the view is disposed. */ private void activateContext() { IContextService contextService = (IContextService) getSite().getService(IContextService.class); contextService.activateContext(VIEW_CONTEXT_ID); } @Override public void propertyChange(PropertyChangeEvent event) { // titleFont = // JFaceResources.getFont("no.resheim.elibrarium.epub.ui.titleFont"); // dateFont = // JFaceResources.getFont("no.resheim.elibrarium.epub.ui.dateFont"); } }