org.netbeans.modules.openfile.DefaultOpenFileImpl Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.openfile;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Element;
import javax.swing.text.StyledDocument;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.actions.FileSystemAction;
import org.openide.actions.ToolsAction;
import org.openide.awt.StatusDisplayer;
import org.openide.cookies.EditCookie;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.ViewCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.Node;
import org.openide.nodes.NodeOperation;
import org.openide.text.NbDocument;
import org.openide.util.ContextAwareAction;
import org.openide.util.Exceptions;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.FINEST;
import static org.openide.cookies.EditorCookie.Observable.PROP_OPENED_PANES;
/**
* Opens files when requested. Main functionality.
*
* @author Jaroslav Tulach, Jesse Glick, Marian Petras, David Konecny
*/
@org.openide.util.lookup.ServiceProvider(service=org.netbeans.modules.openfile.OpenFileImpl.class, position=100)
public class DefaultOpenFileImpl implements OpenFileImpl {
private final Logger log = Logger.getLogger(getClass().getName());
/**
* Sets the specified text into the status line.
*
* @param text text to be displayed
*/
protected final void setStatusLine(String text) {
StatusDisplayer.getDefault().setStatusText(text);
}
/**
* Displays a dialog that the file cannot be open.
* This method is to be used in cases that the file was open via
* the Open File Server. The message also informs that
* the launcher will be notified as if the file
* was closed immediately.
*
* @param fileName name of file that could not be opened
*/
protected void notifyCannotOpen(String fileName) {
assert EventQueue.isDispatchThread();
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
NbBundle.getMessage(DefaultOpenFileImpl.class,
"MSG_cannotOpenWillClose", //NOI18N
fileName)));
}
/**
* Opens an editor using EditorCookie
.
* If non-negative line number is passed, it also places cursor at the given
* line.
*
* @param cookie cookie to use for opening an editor
* @param observable whether the cookie is
* EditorCookie.Observable
* @param line line number to place cursor to (starting at 0
)
* @return true
if the cookie was successfully activated,
* false
if some error occured
*/
private boolean openEditor(final EditorCookie editorCookie,
final int line) {
assert EventQueue.isDispatchThread();
if (log.isLoggable(FINER)) {
log.log(FINER, "openEditor(EditorCookie, line={0})", line); //NOI18N
}
/* if the editor is already open, just set the cursor and activate it */
JEditorPane[] openPanes = editorCookie.getOpenedPanes();
if (openPanes != null) {
log.finest("open pane(s) found"); //NOI18N
if (line >= 0) {
int cursorOffset = getCursorOffset(editorCookie.getDocument(),
line);
openPanes[0].setCaretPosition(cursorOffset);
}
final Container c;
c = SwingUtilities.getAncestorOfClass(TopComponent.class,
openPanes[0]);
if (c != null) {
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
@Override
public void run() {
((TopComponent) c).requestActive();
}
});
} else {
assert false;
}
return true;
}
/* get the document: */
final StyledDocument doc;
try {
doc = editorCookie.openDocument();
} catch (IOException ex) {
String msg = NbBundle.getMessage(
DefaultOpenFileImpl.class,
"MSG_cannotOpenWillClose"); //NOI18N
ErrorManager.getDefault().notify(
ErrorManager.EXCEPTION,
ErrorManager.getDefault().annotate(ex, msg));
return false;
}
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
@Override
public void run() {
editorCookie.open();
/*
* Note: editorCookie.open() may return before the editor is
* actually open. But since the document was successfully open,
* the editor should be opened quite quickly and no problem
* should occur.
*/
if (line >= 0) {
openDocAtLine(editorCookie, doc, line);
}
}
});
return true;
}
/**
* Opens a document in the editor at a given line.
* This method is used in the case that the editor is not opened yet
* (EditorCookie.getOpenedPanes()
returned null
)
* and is to be opened at a specific line.
*
* @param editorCookie editor cookie to use for opening the document
* @param doc document already loaded using the editor cookie
* @param line line to open the document at (first line = 0
);
* must be non-negative
*/
private void openDocAtLine(final EditorCookie editorCookie,
final StyledDocument doc,
final int line) {
assert EventQueue.isDispatchThread();
assert line >= 0;
assert editorCookie.getDocument() == doc;
if (log.isLoggable(FINER)) {
log.log(FINER, "openDocAtLine(EditorCookie, Document, line={0})", line);
}
int offset = getCursorOffset(doc, line);
new SetCursorTask(editorCookie, offset).perform();
}
final class SetCursorTask implements Runnable, PropertyChangeListener {
/**
* if opening file using non-observable {@code EditorCookie},
* how long should we wait (in milliseconds) between tries?
*/
private static final int OPEN_EDITOR_WAIT_PERIOD_MS = 200;
/**
* if opening file using non-observable {@code EditorCookie},
* how long should we wait (in milliseconds) in total before
* giving up?
*/
private static final int OPEN_EDITOR_TOTAL_TIMEOUT_MS = 10000;
private static final int MAX_TRIES = OPEN_EDITOR_TOTAL_TIMEOUT_MS
/ OPEN_EDITOR_WAIT_PERIOD_MS;
private final EditorCookie editorCookie;
private final EditorCookie.Observable observable;
private final int offset;
private volatile boolean success = false;
private SetCursorTask(EditorCookie editorCookie, int offset) {
this.editorCookie = editorCookie;
this.observable = (editorCookie instanceof EditorCookie.Observable)
? (EditorCookie.Observable) editorCookie
: null;
this.offset = offset;
if (log.isLoggable(FINEST)) {
log.finest("SetCursorTask."); //NOI18N
log.log(FINEST, " - observable: {0}", (observable != null));//NOI18N
}
}
private void perform() {
log.finer("SetCursorTask: perform()"); //NOI18N
log.finest("SetCursorTask: Calling tryNow() for the first time...");//NOI18N
if (tryNow()) {
log.finest("SetCursorTask: SUCCESS!"); //NOI18N
return;
}
if (observable != null) {
log.finest("SetCursorTask: addPropertyChangeListener...");//NOI18N
observable.addPropertyChangeListener(this);
/*
* We must try after we started listening, otherwise
* we might miss the moment the pane was opened.
*/
log.finest("SetCursorTask: tryNow() after adding the listener...");//NOI18N
if (tryNow()) {
log.finest("SetCursorTask: SUCCESS!"); //NOI18N
}
} else {
trySeveralTimes();
}
}
private boolean tryNow() {
assert !success;
JEditorPane[] panes = editorCookie.getOpenedPanes();
if (panes != null) {
this.success = true;
panes[0].setCaretPosition(offset);
return true;
} else {
return false;
}
}
@Override
public void propertyChange(PropertyChangeEvent e) {
log.finer("SetCursorTask: propertyChange()"); //NOI18N
assert PROP_OPENED_PANES.equals(e.getPropertyName());
observable.removePropertyChangeListener(this);
Mutex.EVENT.writeAccess(this);
}
private void trySeveralTimes() {
log.finest("SetCursorTask: trySeveralTimes()"); //NOI18N
RequestProcessor.getDefault().post(new ScheduledOpenTask(),
OPEN_EDITOR_WAIT_PERIOD_MS);
}
class ScheduledOpenTask implements Runnable {
private volatile int remainingTries = MAX_TRIES;
@Override
public void run() {
try {
EventQueue.invokeAndWait(SetCursorTask.this);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} catch (InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
if (!SetCursorTask.this.success) {
if (--remainingTries != 0) {
RequestProcessor.getDefault()
.post(this, OPEN_EDITOR_WAIT_PERIOD_MS);
} else {
notifyCouldNotOpen();
}
}
}
}
@Override
public void run() {
assert EventQueue.isDispatchThread();
log.finer("SetCursorTask: run()"); //NOI18N
if (success) {
/*
* This may happen e.g. if the editor pane is opened during
* the period between call of addPropertyChangeListener(...)
* and the second call of tryNow() in method perform().
*/
log.finest("SetCursorTask: - already done"); //NOI18N
return;
}
log.finest("SetCursorTask: tryNow() from run()"); //NOI18N
tryNow();
if ((observable != null) && !success) {
assert false;
notifyCouldNotOpen();
}
}
private void notifyCouldNotOpen() {
DialogDisplayer.getDefault().notifyLater(
new NotifyDescriptor.Message(
NbBundle.getMessage(
DefaultOpenFileImpl.class,
"MSG_couldNotOpenAt"), //NOI18N
NotifyDescriptor.INFORMATION_MESSAGE));
}
}
/**
* Computes cursor offset of a given line of a document.
* The line number must be non-negative.
* If the line number is greater than number of the last line,
* the returned offset corresponds to the last line of the document.
*
* @param doc document to computer offset for
* @param line line number (first line = 0
)
* @return cursor offset of the beginning of the given line
*/
private static int getCursorOffset(StyledDocument doc, int line) {
assert EventQueue.isDispatchThread();
assert line >= 0;
try {
return NbDocument.findLineOffset(doc, line);
} catch (IndexOutOfBoundsException ex) {
/* probably line number out of bounds */
Element lineRootElement
= NbDocument.findLineRootElement(doc);
int lineCount = lineRootElement.getElementCount();
if (line >= lineCount) {
return NbDocument.findLineOffset(doc, lineCount - 1);
} else {
throw ex;
}
}
}
/**
* Activates the specified cookie, thus opening a file.
* The file is specified by the cookie, because the cookie was obtained
* from it. The cookie must be one of EditorCookie
* OpenCookie
, EditCookie
,
* ViewCookie
.
*
* @param cookie cookie to activate
* @param cookieClass type of the cookie - specifies action to activate
* @param line used only by EditorCookie
s -
* specifies initial line to open the file at
* @return true
if the cookie was successfully activated,
* false
if some error occured
* @exception java.lang.IllegalArgumentException
* if cookieClass
is not any of
* EditorCookie
, OpenCookie
,
* ViewCookie
* @exception java.lang.ClassCastException
* if the cookie
is not an instance
* of the specified cookie class
*/
protected boolean openByCookie(Node.Cookie cookie,
Class cookieClass,
final int line) {
assert EventQueue.isDispatchThread();
if ((cookieClass == EditorCookie.Observable.class)
|| (cookieClass == EditorCookie.class)) {
return openEditor((EditorCookie) cookie, line);
} else if (cookieClass == OpenCookie.class) {
((OpenCookie) cookie).open();
} else if (cookieClass == EditCookie.class) {
((EditCookie) cookie).edit();
} else if (cookieClass == ViewCookie.class) {
((ViewCookie) cookie).view();
} else {
throw new IllegalArgumentException();
}
return true;
}
/**
* Tries to open the specified file, using one of EditorCookie
,
* OpenCookie
, EditCookie
, ViewCookie
* (in the same order).
* If the client of the open file server wants, waits until the file is
* closed and notifies the client.
*
* @param dataObject DataObject
representing the file
* @param line if EditorCookie
is used,
* specifies initial line to open the file at
* @return true
if the file was successfully open,
* false
otherwise
*/
private boolean openDataObjectByCookie(DataObject dataObject,
int line) {
Class extends Node.Cookie> cookieClass;
Node.Cookie cookie;
if ( (cookie = dataObject.getCookie(cookieClass = OpenCookie.class)) != null
|| (cookie = dataObject.getCookie(cookieClass = EditCookie.class)) != null
|| (cookie = dataObject.getCookie(cookieClass = ViewCookie.class)) != null) {
return openByCookie(cookie, cookieClass, line);
}
return false;
}
/**
* Opens the FileObject
either by calling {@link EditorCookie}
* (or {@link OpenCookie} or {@link ViewCookie}),
* or by showing it in the Explorer.
*/
@Override
public boolean open(final FileObject fileObject, final int line) {
if (log.isLoggable(FINER)) {
log.log(FINER, "open({0}, line={1}) called from thread {2}",//NOI18N
new Object[]{fileObject.getNameExt(),
line, Thread.currentThread().getName()});
}
/* Find a DataObject for the FileObject: */
final DataObject dataObject;
try {
dataObject = DataObject.find(fileObject);
} catch (DataObjectNotFoundException ex) {
ErrorManager.getDefault().notify(ex);
return false;
}
Mutex.EVENT.writeAccess(new Runnable() {
@Override
public void run() {
openInEDT(fileObject, dataObject, line);
}
});
return true;
}
private void openInEDT(FileObject fileObject, DataObject dataObject, int line) {
Class extends Node.Cookie> cookieClass;
Node.Cookie cookie;
if ((line != -1)
&& ( ((cookie = dataObject.getCookie(cookieClass = EditorCookie.Observable.class)) != null)
|| ((cookie = dataObject.getCookie(cookieClass = EditorCookie.class)) != null) )) {
boolean ret = openByCookie(cookie, cookieClass, line);
if (!ret) {
showNotPlainFileWarning(fileObject);
}
return;
}
/* try to open the object using the default action */
Node dataNode = dataObject.getNodeDelegate();
Action action = dataNode.getPreferredAction();
if ((action != null)
&& !(action instanceof FileSystemAction)
&& !(action instanceof ToolsAction)) {
if (log.isLoggable(FINEST)) {
log.log(FINEST, " - using preferred action " //NOI18N
+ "(\"{0}\" - {1}) for opening the file", //NOI18N
new Object[]{action.getValue(Action.NAME),
action.getClass().getName()});
}
if (action instanceof ContextAwareAction) {
action = ((ContextAwareAction) action)
.createContextAwareInstance(dataNode.getLookup());
if (log.isLoggable(FINEST)) {
log.finest(" - it is a ContextAwareAction"); //NOI18N
log.log(FINEST, " - using a context-aware " //NOI18N
+ "instance instead (\"{0}\" - {1})", //NOI18N
new Object[]{action.getValue(Action.NAME),
action.getClass().getName()});
}
}
log.finest(" - will call action.actionPerformed(...)"); //NOI18N
final Action a = action;
final Node n = dataNode;
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
@Override
public void run() {
a.actionPerformed(new ActionEvent(n, 0, ""));
}
});
return;
}
String fileName = fileObject.getNameExt();
/* Try to grab an editor/open/edit/view cookie and open the object: */
StatusDisplayer.getDefault().setStatusText(
NbBundle.getMessage(DefaultOpenFileImpl.class,
"MSG_opening", //NOI18N
fileName));
boolean success = openDataObjectByCookie(dataObject, line);
if (success) {
return;
}
if (fileObject.isFolder() || FileUtil.isArchiveFile(fileObject)) {
// select it in explorer:
final Node node = dataObject.getNodeDelegate();
if (node != null) {
WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
@Override
public void run() {
NodeOperation.getDefault().explore(node);
}
});
return;
}
}
showNotPlainFileWarning(fileObject);
}
private void showNotPlainFileWarning(FileObject fileObject) {
String msg = NbBundle.getMessage(OpenFile.class,
"MSG_FileIsNotPlainFile", fileObject); //NOI18N
DefaultExternalDropHandler.showWarningMessageFileNotOpened(msg);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy