/*
* NotesView.java
*
* Copyright (c) 2007
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dcarew.notes.views;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.dcarew.notes.NotesFileUtils;
import org.dcarew.notes.NotesPlugin;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.action.Action;
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.dialogs.MessageDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.DefaultTextDoubleClickStrategy;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.IUndoManagerExtension;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.TextViewerUndoManager;
import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.URLHyperlinkDetector;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
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.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
import org.eclipse.ui.operations.RedoActionHandler;
import org.eclipse.ui.operations.UndoActionHandler;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.IUpdate;
// TODO: Change the save location? Save in the user's home directory?
// TODO: bold, italic
// TODO: bullet list? task list, w/ finished / unfinished items?
// TODO: find dialog (see FindReplaceAction, TextConsolePage)
// TODO: shift left and right
// TODO: print?
// TODO: auto-indent strategy?
// TODO: We lost underlining of urls - fix it.
// TODO: dirty flag should use the operations history
/**
* This view acts like a simple scratch-pad like text editor. It is similar to the old Macintosh
* Sitckies application. The user never needs to explicitly save the note text; it is automatically
* saved and restored on application termination and startup.
*
* @author Devon Carew
*/
public class NotesView
extends ViewPart
{
private static final String TOP_LINE_INDEX = "topLineIndex";
private static Color NOTE_COLOR;
private TextViewer textViewer;
private IMemento memento;
private Action action1;
private Action exportAction;
private boolean dirty;
private UndoActionHandler undoAction;
private RedoActionHandler redoAction;
private IUndoContext undoContext;
private Map textActions = new HashMap();
/** We listen for part deactivated events and perform an auto-save. */
private IPartListener partListener = new IPartListener() {
public void partActivated(IWorkbenchPart part) {
}
public void partBroughtToTop(IWorkbenchPart part) {
}
public void partClosed(IWorkbenchPart part) {
}
public void partDeactivated(IWorkbenchPart part) {
if (part == NotesView.this)
saveNotesText();
}
public void partOpened(IWorkbenchPart part) {
}
};
/**
* Create a new Notes instance.
*/
public NotesView()
{
}
public Object getAdapter(Class adapter)
{
if (IFindReplaceTarget.class.equals(adapter))
return textViewer == null ? null : textViewer.getFindReplaceTarget();
if (ITextOperationTarget.class.equals(adapter))
return textViewer == null ? null : textViewer.getTextOperationTarget();
if (IRewriteTarget.class.equals(adapter))
return ((ITextViewerExtension) textViewer).getRewriteTarget();
if (Control.class.equals(adapter))
return textViewer != null ? textViewer.getTextWidget() : null;
return super.getAdapter(adapter);
}
/**
* This is a callback that will allow us to create the viewer and initialize it.
*/
public void createPartControl(Composite parent)
{
if (NOTE_COLOR == null)
NOTE_COLOR = new Color(parent.getDisplay(), 255, 255, 180);
GridLayout layout = new GridLayout(1, false);
layout.marginLeft = 3;
layout.marginWidth = 0;
layout.marginHeight = 0;
parent.setLayout(layout);
parent.setBackground(NOTE_COLOR);
textViewer = new TextViewer(parent, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL);
textViewer.getControl().setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT));
textViewer.getControl().setBackground(NOTE_COLOR);
textViewer.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
textViewer.setDocument(new Document());
textViewer.setHyperlinkPresenter(new DefaultHyperlinkPresenter(new RGB(0, 0, 255)));
textViewer.setHyperlinkDetectors(new IHyperlinkDetector[] { new URLHyperlinkDetector() }, SWT.MOD1);
textViewer.setUndoManager(new TextViewerUndoManager(100));
textViewer.setTextDoubleClickStrategy(new DefaultTextDoubleClickStrategy(), IDocument.DEFAULT_CONTENT_TYPE);
textViewer.getDocument().addDocumentListener(new IDocumentListener() {
public void documentAboutToBeChanged(DocumentEvent event) {
}
public void documentChanged(DocumentEvent event) {
setDirty(true);
updateActions();
}
});
textViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
updateActions();
}
});
StyledText styleText = textViewer.getTextWidget();
styleText.setEditable(true);
styleText.setEnabled(true);
styleText.setTabs(4);
PresentationReconciler presentationReconciler = new PresentationReconciler();
presentationReconciler.install(textViewer);
// Enable the TextViewerUndoManager.
textViewer.activatePlugins();
makeActions();
hookContextMenu();
createGlobalActionHandlers();
contributeToActionBars();
loadNotesText();
restoreUISettings(this.memento);
updateActions();
}
private void hookContextMenu()
{
MenuManager menuMgr = new MenuManager("#PopupMenu");
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
NotesView.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(textViewer.getControl());
textViewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, textViewer);
}
private void contributeToActionBars()
{
IActionBars bars = getViewSite().getActionBars();
fillLocalToolBar(bars.getToolBarManager());
fillLocalPullDown(bars.getMenuManager());
}
private void fillContextMenu(IMenuManager manager)
{
manager.add(getAction(ActionFactory.CUT.getId()));
manager.add(getAction(ActionFactory.COPY.getId()));
manager.add(getAction(ActionFactory.PASTE.getId()));
manager.add(new Separator());
manager.add(getAction(ActionFactory.DELETE.getId()));
manager.add(getAction(ActionFactory.SELECT_ALL.getId()));
// manager.add(new Separator());
// manager.add(getAction(ActionFactory.FIND.getId()));
// Other plug-ins can contribute there actions here
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private void fillLocalToolBar(IToolBarManager manager)
{
//manager.add(action1);
}
private void fillLocalPullDown(final IMenuManager manager)
{
manager.add(exportAction);
}
private void createGlobalActionHandlers()
{
undoContext = ((IUndoManagerExtension)textViewer.getUndoManager()).getUndoContext();
// set up action handlers that operate on the current context
undoAction = new UndoActionHandler(getSite(), undoContext);
redoAction = new RedoActionHandler(getSite(), undoContext);
IActionBars actionBars = getViewSite().getActionBars();
actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
// Install the standard text actions.
addTextAction(ActionFactory.CUT, ITextOperationTarget.CUT);
addTextAction(ActionFactory.COPY, ITextOperationTarget.COPY);
addTextAction(ActionFactory.PASTE, ITextOperationTarget.PASTE);
addTextAction(ActionFactory.DELETE, ITextOperationTarget.DELETE);
addTextAction(ActionFactory.SELECT_ALL, ITextOperationTarget.SELECT_ALL);
}
private void updateActions()
{
for (Iterator itor = textActions.values().iterator(); itor.hasNext(); )
{
((IUpdate)itor.next()).update();
}
}
protected void addTextAction(ActionFactory actionFactory, int textOperation)
{
IWorkbenchWindow window = getViewSite().getWorkbenchWindow();
IWorkbenchAction globalAction = actionFactory.create(window);
// Create our text action.
TextViewerAction textAction = new TextViewerAction(textOperation);
textActions.put(actionFactory.getId(), textAction);
// Copy its properties from the global action.
textAction.setText(globalAction.getText());
textAction.setToolTipText(globalAction.getToolTipText());
textAction.setDescription(globalAction.getDescription());
textAction.setImageDescriptor(globalAction.getImageDescriptor());
textAction.setDisabledImageDescriptor(globalAction.getDisabledImageDescriptor());
textAction.setAccelerator(globalAction.getAccelerator());
// Make sure it's up to date.
textAction.update();
// Register our text action with the global action handler.
IActionBars actionBars = getViewSite().getActionBars();
actionBars.setGlobalActionHandler(actionFactory.getId(), textAction);
}
protected TextViewerAction getAction(String actionID)
{
return (TextViewerAction)textActions.get(actionID);
}
class TextViewerAction
extends Action
implements IUpdate
{
private int actionId;
TextViewerAction(int actionId)
{
this.actionId = actionId;
}
public boolean isEnabled()
{
return textViewer.canDoOperation(actionId);
}
public void run()
{
textViewer.doOperation(actionId);
updateActions();
}
public void update()
{
if (super.isEnabled() != isEnabled())
setEnabled(isEnabled());
}
}
private void makeActions()
{
action1 = new Action() {
public void run() {
showMessage("Action 1 executed");
}
};
action1.setText("Action 1");
action1.setToolTipText("Action 1 tooltip");
action1.setImageDescriptor(
PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
exportAction = createExportAction();
}
private void showMessage(String message)
{
MessageDialog.openInformation(textViewer.getControl().getShell(), "Notes", message);
}
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus()
{
textViewer.getControl().setFocus();
}
public void init(IViewSite site, IMemento memento)
throws PartInitException
{
super.init(site, memento);
this.memento = memento;
site.getPage().addPartListener(partListener);
}
public void dispose()
{
getSite().getPage().removePartListener(partListener);
saveNotesText();
super.dispose();
}
public void saveState(IMemento memento)
{
saveUISettings(memento);
super.saveState(memento);
}
private void saveUISettings(IMemento memento)
{
memento.putInteger(TOP_LINE_INDEX, textViewer.getTopIndex());
}
private void restoreUISettings(IMemento memento)
{
if (memento == null)
return;
Integer topLineIndex = memento.getInteger(TOP_LINE_INDEX);
// TODO: This plus one is due to a bug where getting / setting the top line index is off by one.
if (topLineIndex != null && topLineIndex.intValue() != -1)
textViewer.setTopIndex(topLineIndex.intValue() + 1);
}
private IPath getDataPath()
{
IPath pluginDir = NotesPlugin.getDefault().getStateLocation();
return pluginDir.append("notes.txt");
}
public boolean isDirty()
{
return dirty;
}
private void setDirty(boolean dirty)
{
if (this.dirty != dirty)
this.dirty = dirty;
}
private void loadNotesText()
{
IPath dataPath = getDataPath();
File file = dataPath.toFile();
if (!file.exists() || !file.canRead())
return;
try
{
String text = NotesFileUtils.readTextUTF8(file);
if (text != null)
{
textViewer.getDocument().set(text);
setDirty(false);
// Reset undo history.
textViewer.resetPlugins();
}
}
catch (IOException ioe)
{
NotesPlugin.log(ioe);
}
}
private void saveNotesText()
{
if (!isDirty())
return;
IPath dataPath = getDataPath();
File file = dataPath.toFile();
String text = textViewer.getDocument().get();
try
{
NotesFileUtils.writeTextUTF8(text, file);
setDirty(false);
}
catch (IOException ioe)
{
NotesPlugin.log(ioe);
}
}
private Action createExportAction()
{
Action action = new Action() {
public void run() {
FileDialog saveDialog = new FileDialog(getSite().getShell(), SWT.SAVE);
saveDialog.setFilterExtensions(new String[] { "*.txt" });
String absPathName = saveDialog.open();
if (absPathName != null)
{
try
{
String text = textViewer.getDocument().get();
NotesFileUtils.writeTextUTF8(text, new File(absPathName));
}
catch (IOException ioe)
{
MessageDialog.openError(getSite().getShell(),
"Error Exporting Text", "Unable to export Notes text: " + ioe.getCause());
}
}
}
};
action.setText("Export...");
action.setToolTipText("Export the Notes Text");
action.setImageDescriptor(NotesPlugin.getImageDescriptor("icons/etool16/importdir_wiz.gif"));
return action;
}
}