/*
* Copyright (C) 2007 SQL Explorer Development Team http://sourceforge.net/projects/eclipsesql
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package net.sourceforge.sqlexplorer.plugin.editors;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import net.sourceforge.sqlexplorer.IConstants;
import net.sourceforge.sqlexplorer.Messages;
import net.sourceforge.sqlexplorer.connections.ConnectionsView;
import net.sourceforge.sqlexplorer.connections.SessionEstablishedAdapter;
import net.sourceforge.sqlexplorer.dbproduct.SQLConnection;
import net.sourceforge.sqlexplorer.dbproduct.Session;
import net.sourceforge.sqlexplorer.dbproduct.User;
import net.sourceforge.sqlexplorer.plugin.SQLExplorerPlugin;
import net.sourceforge.sqlexplorer.sessiontree.model.utility.Dictionary;
import net.sourceforge.sqlexplorer.sqleditor.actions.SQLEditorToolBar;
import net.sourceforge.sqlexplorer.sqleditor.results.ResultsTab;
import net.sourceforge.sqlexplorer.sqlpanel.AbstractSQLExecution;
import net.sourceforge.sqlexplorer.util.PartAdapter2;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
import org.talend.core.model.properties.PropertiesPackage;
import org.talend.core.model.properties.Property;
/**
* SQLEditor is the top-level Editor component which is registered with Eclipse as the Editor for each new SQL
* connection.
*
* NOTE RE PREVIOUS VERSIONS: The previous version of this class was derived from TextEditor and "only" provided
* facilities for editing and executing SQL text; the results were displayed in a separate view, where the results from
* different editors would intermingle. Also, any messages output from the server (ie which was not a result set) were
* lost.
*
* This version includes a splitter pane where the top half is the text editor and the bottom half is a tabbed control
* containing one tab per result set and a further tab for messages. The code in SQLEditor was becoming very large and
* so the code for the text editor part has been split off into SQLTextEditor; it is largely unchanged except for the
* refactoring.
*
* Another change is that the editor no longer provides a status bar; because each query tab provides a status bar with
* a message specific to the resultset, it was redundant to provide the same info (well, "most recent event" info) in a
* status bar just 2 pixels below. However, the setMessage method is still supported and now updates the main Eclipse
* status bar instead. This means that the "limit rows" feature moves to the Editor toolbar.
*
* Behavoural Changes The editors now prompt to save when they're closed; this is part of the slight paradigm shift of
* SQLExplorer - instead of the SQL window being a scratch pad for executing ad-hoc SQL, it's becoming part of a
* development environment for stored procedures and queries.
*
* @modified John Spackman
*
*/
public class SQLEditor extends EditorPart implements SwitchableSessionEditor {
// The color of the sash
private static final Color SASH_COLOR = IConstants.TAB_BORDER_COLOR;
// Typical file extensions
public static final String[] SUPPORTED_FILETYPES = new String[] { "*.sql", "*.txt", "*.*" };
public static final String EDITOR_ID = "net.sourceforge.sqlexplorer.plugin.editors.SQLEditor";
// The Session node from the Connections view
private Session session;
// Toolbar
private SQLEditorToolBar toolBar;
// The actual text editor for the top half of our composite editor
private SQLTextEditor textEditor;
// TabFolder for results and messages; note that we use CTabFolder and not TabFolder
// because CTabFolder adds support for an "X" close button on each tab
private CTabFolder tabFolder;
// The messages tab
private CTabItem messagesTab;
// The Table in the Messages tab
private Table messagesTable;
// Our own dirty flag - used for new files
private boolean isDirty;
// True if the editor does not have a filename yet
private boolean isUntitled;
final static Color firstColor = new Color(null, 211, 225, 250);
final static Color secondColor = new Color(null, 175, 201, 246);
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IEditorPart#init(org.eclipse.ui.IEditorSite, org.eclipse.ui.IEditorInput)
*/
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
// Configure the editor
setSite(site);
// PTODO qzhang fixed bug 3904: syschronize the resource.
if (input instanceof FileEditorInput) {
try {
((FileEditorInput) input).getFile().getParent().refreshLocal(IResource.DEPTH_ONE, null);
} catch (CoreException e) {
e.printStackTrace();
}
}
setInput(input);
// Create the text editor
textEditor = new SQLTextEditor(this);
textEditor.init(site, input);
// setPartName(getSite().getPage().getLabel());
// Make sure we get notification that our editor is closing because
// we may need to stop running queries
getSite().getPage().addPartListener(new PartAdapter2() {
/*
* (non-JavaDoc)
*
* @see net.sourceforge.sqlexplorer.util.PartAdapter2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
*/
@Override
public void partClosed(IWorkbenchPartReference partRef) {
if (partRef.getPart(false) == SQLEditor.this) {
onCloseEditor();
}
}
});
// If we havn't got a view, then try for the current session in the ConnectionsView
if (getSession() == null) {
ConnectionsView view = SQLExplorerPlugin.getDefault().getConnectionsView();
if (view != null) {
User user = view.getDefaultUser();
if (user != null) {
user.queueForNewSession(new SessionEstablishedAdapter() {
@Override
public void sessionEstablished(Session session) {
setSession(session);
}
});
}
}
}
}
@Override
public void dispose() {
setSession(null);
if (this.textEditor.getTitleImage() != null) {
this.textEditor.getTitleImage().dispose();
}
super.dispose();
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createPartControl(Composite parent) {
try {
parent.setLayout(new FillLayout());
// Create a wrapper for our stuff
final Composite myParent = new Composite(parent, SWT.NONE);
FormLayout layout = new FormLayout();
myParent.setLayout(layout);
FormData data;
// Create sash and attach it to 75% of the way down
final Sash sash = createSash(myParent);
data = new FormData();
// MOD scorreia 2008-10-30 enhance result tab height
// data.top = new FormAttachment(75, 0);
data.top = new FormAttachment(20, 0);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
sash.setLayoutData(data);
// Create status bar and attach it to the bottom
/*
* Composite statusBar = createStatusBar(myParent); data = new FormData(); data.left = new FormAttachment(0,
* 0); data.right = new FormAttachment(100, 0); data.bottom = new FormAttachment(100, 0);
* statusBar.setLayoutData(data);
*/
// Create the toolbar and attach it to the top of the composite
createToolbar(myParent);
data = new FormData();
data.top = new FormAttachment(0, 0);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
toolBar.getToolbarControl().setLayoutData(data);
// Attach the editor to the toolbar and the top of the sash
final Composite editor = createEditor(myParent);
data = new FormData();
data.top = new FormAttachment(toolBar.getToolbarControl(), 0);
data.bottom = new FormAttachment(sash, 0);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
editor.setLayoutData(data);
// Attach the tabs to the bottom of the sash and the bottom of the composite
CTabFolder tabFolder = createResultTabs(myParent);
data = new FormData();
data.top = new FormAttachment(sash, 0);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
data.bottom = new FormAttachment(100, 0);
tabFolder.setLayoutData(data);
if (session != null) {
toolBar.onEditorSessionChanged(session);
}
} catch (Exception e) {
SQLExplorerPlugin.error("Couldn't create text editor", e);
MessageDialog.openError(getSite().getShell(), Messages.getString("SQLEditor.Init.CreateTextEditor"), e.getClass()
.getCanonicalName() + ": " + e.getMessage());
}
}
/**
* Creates the toolbar
*
* @param parent
*/
private void createToolbar(final Composite parent) {
toolBar = new SQLEditorToolBar(parent, this);
toolBar.addResizeListener(new ControlListener() {
@Override
public void controlMoved(ControlEvent e) {
}
@Override
public void controlResized(ControlEvent e) {
parent.getParent().layout(true);
parent.layout(true);
}
});
}
/**
* Creates the sash (the draggable splitter) between the editor and the results tab
*
* @param parent
* @return
*/
private Sash createSash(Composite parent) {
// Create the sash and put it in the middle
final Sash sash = new Sash(parent, SWT.HORIZONTAL);
sash.setBackground(SASH_COLOR);
sash.addSelectionListener(new SelectionListener() {
/*
* (non-JavaDoc)
*
* @see
* org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e) {
FormData data = (FormData) sash.getLayoutData();
Rectangle rect = sash.getParent().getBounds();
data.top = new FormAttachment(e.y, rect.height, 0);
sash.getParent().layout();
sash.getParent().getParent().layout();
}
});
return sash;
}
/**
* Creates the text editor in the top half
*
* @param parent
* @return
*/
private Composite createEditor(Composite parent) {
// Attach the editor to the top of the composite and the top of the sash
final Composite editorParent = new Composite(parent, SWT.NONE);
editorParent.setLayout(new FillLayout());
textEditor.createPartControl(editorParent);
textEditor.addPropertyListener(new IPropertyListener() {
@Override
public void propertyChanged(Object source, int propertyId) {
SQLEditor.this.firePropertyChange(propertyId);
}
});
return editorParent;
}
/**
* Creates the results tabs in the bottom half
*
* @param parent
* @return
*/
private CTabFolder createResultTabs(Composite parent) {
tabFolder = new CTabFolder(parent, SWT.TOP | SWT.CLOSE);
tabFolder.setBorderVisible(true);
tabFolder.setLayoutData(new GridData(GridData.FILL_BOTH));
// Set up a gradient background for the selected tab
Display display = getSite().getShell().getDisplay();
tabFolder.setSelectionBackground(new Color[] { display.getSystemColor(SWT.COLOR_WHITE), firstColor, secondColor,
IConstants.TAB_BORDER_COLOR }, new int[] { 25, 50, 75 }, true);
messagesTab = new CTabItem(tabFolder, SWT.NONE);
messagesTab.setText(Messages.getString("SQLEditor.Results.Messages.Caption"));
messagesTable = new Table(tabFolder, SWT.SINGLE | SWT.BORDER | SWT.FULL_SELECTION);
messagesTab.setControl(messagesTable);
messagesTable.setLinesVisible(true);
messagesTable.setHeaderVisible(true);
messagesTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
Message message = (Message) ((TableItem) e.item).getData();
setCursorPosition(message.getLineNo(), message.getCharNo());
}
});
TableColumn col = new TableColumn(messagesTable, SWT.NONE);
col.setText(Messages.getString("SQLEditor.Results.Messages.Status"));
col.pack();
col = new TableColumn(messagesTable, SWT.NONE);
col.setText(Messages.getString("SQLEditor.Results.Messages.Location"));
col.pack();
col = new TableColumn(messagesTable, SWT.NONE);
col.setText(Messages.getString("SQLEditor.Results.Messages.SQL"));
col.pack();
col = new TableColumn(messagesTable, SWT.NONE);
col.setText(Messages.getString("SQLEditor.Results.Messages.Text"));
col.pack();
tabFolder.setSelection(messagesTab);
// Add a listener to get the close button on each tab
tabFolder.addCTabFolder2Listener(new CTabFolder2Adapter() {
/*
* (non-JavaDoc)
*
* @see org.eclipse.swt.custom.CTabFolder2Adapter#close(org.eclipse.swt.custom.CTabFolderEvent)
*/
@Override
public void close(CTabFolderEvent event) {
super.close(event);
CTabItem tabItem = (CTabItem) event.item;
event.doit = onCloseTab(tabItem);
}
});
return tabFolder;
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
*/
@Override
protected void setInput(IEditorInput input) {
super.setInput(input);
if (textEditor != null) {
textEditor.setInput(input);
}
// Handle our own form of input
if (input instanceof SQLEditorInput) {
SQLEditorInput sqlInput = (SQLEditorInput) input;
if (input != null) {
User user = sqlInput.getUser();
if (user != null) {
user.queueForNewSession(new SessionEstablishedAdapter() {
@Override
public void sessionEstablished(Session session) {
setSession(session);
}
});
}
isDirty = true;
isUntitled = true;
}
}
// set part name as displayName + " " + version
String partName = input.getName();
if (input instanceof FileEditorInput) {
FileEditorInput fileEditorInput = (FileEditorInput) input;
IPath fileName = fileEditorInput.getPath().removeFileExtension().addFileExtension("properties"); //$NON-NLS-1$
Property property = null;
URI propURI = URI.createFileURI(fileName.toOSString());
Resource resource = new ResourceSetImpl().getResource(propURI, true);
if (resource.getContents() != null) {
Object object = EcoreUtil.getObjectByType(resource.getContents(), PropertiesPackage.eINSTANCE.getProperty());
if (object != null) {
property = (Property) object;
}
}
if (property != null) {
partName = property.getDisplayName() + " " + property.getVersion(); //$NON-NLS-1$
}
}
setPartName(partName);
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void doSave(IProgressMonitor monitor) {
if (monitor == null) {
monitor = textEditor.getProgressMonitor();
}
if (isUntitled) {
if (!doSave(false, monitor)) {
monitor.setCanceled(true);
}
return;
}
// If it's a SQLEditorInput then we have to handle saving ourselves; once the file
// has been saved into a project (via SaveAs) then the input becomes IFileEditorInput
// and Eclipse knows how to deal with it
IEditorInput input = getEditorInput();
if (input instanceof SQLEditorInput) {
try {
SQLEditorInput sqlInput = (SQLEditorInput) input;
saveToFile(sqlInput.getFile());
} catch (IOException e) {
SQLExplorerPlugin.error(e);
monitor.setCanceled(true);
return;
}
} else {
textEditor.doSave(monitor);
}
setIsDirty(textEditor.isDirty());
}
/**
* Save editor content to file.
*
* @see org.eclipse.ui.ISaveablePart#doSaveAs()
*/
@Override
public void doSaveAs() {
doSave(true, null);
}
/**
* Implementation for save-as; returns true if successfull, false if not (i.e. the user cancelled the dialog)
*
* @return true if saved, false if cancelled
*/
public boolean doSave(boolean saveAs, IProgressMonitor monitor) {
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
boolean haveProjects = projects != null && projects.length > 0;
IEditorInput input = getEditorInput();
boolean saveInsideProject = true;
File file = null;
if (input instanceof SQLEditorInput) {
SQLEditorInput seInput = (SQLEditorInput) input;
file = seInput.getFile();
}
// If we have a file, then we already have a filename outside of the project;
// but if we're doing a save-as then recheck with the user
if (file != null && !saveAs) {
saveInsideProject = false;
} else if (input instanceof SQLEditorInput) {
IConstants.Confirm confirm = SQLExplorerPlugin.getConfirm(IConstants.CONFIRM_YNA_SAVING_INSIDE_PROJECT);
// If we're supposed to ask the user...
if (confirm == IConstants.Confirm.ASK) {
// Build up the message to ask
String msg = Messages.getString("Confirm.SaveInsideProject.Intro") + "\n\n";
if (!haveProjects) {
msg = msg + Messages.getString("Confirm.SaveInsideProject.NoProjectsConfigured") + "\n\n";
}
msg = msg + Messages.getString("Confirm.SaveInsideProject.SaveInProject");
// Ask them
MessageDialogWithToggle dialog = MessageDialogWithToggle.openYesNoCancelQuestion(getSite().getShell(),
Messages.getString("SQLEditor.SaveAsDialog.Title"), msg,
Messages.getString("Confirm.SaveInsideProject.Toggle"), false, null, null);
if (dialog.getReturnCode() == IDialogConstants.CANCEL_ID) {
return false;
}
// If they turned on the toggle ("Use this answer in the future"), update the preferences
if (dialog.getToggleState()) {
confirm = dialog.getReturnCode() == IDialogConstants.YES_ID ? IConstants.Confirm.YES : IConstants.Confirm.NO;
SQLExplorerPlugin.getDefault().getPreferenceStore()
.setValue(IConstants.CONFIRM_YNA_SAVING_INSIDE_PROJECT, confirm.toString());
}
// Whether to save inside or outside
saveInsideProject = dialog.getReturnCode() == IDialogConstants.YES_ID;
} else {
saveInsideProject = confirm == IConstants.Confirm.YES;
}
}
// Saving inside a project - convert SQLEditorInput into a Resource by letting TextEditor do the work for us
if (saveInsideProject) {
if (!haveProjects) {
MessageDialog.openError(getSite().getShell(), Messages.getString("Confirm.SaveInsideProject.Title"),
Messages.getString("Confirm.SaveInsideProject.CreateAProject"));
return false;
}
if (input instanceof SQLEditorInput) {
saveAs = true;
}
// Save it and use their EditorInput
if (saveAs) {
textEditor.doSaveAs();
} else {
if (monitor == null) {
monitor = textEditor.getProgressMonitor();
}
textEditor.doSave(monitor);
}
if (input.equals(textEditor.getEditorInput())) {
return false;
}
input = textEditor.getEditorInput();
setInput(input);
// Update the display
setPartName(input.getName());
setTitleToolTip(input.getToolTipText());
} else {
try {
if (file == null || saveAs) {
FileDialog dialog = new FileDialog(getSite().getShell(), SWT.SAVE);
dialog.setText(Messages.getString("SQLEditor.SaveAsDialog.Title"));
dialog.setFilterExtensions(SUPPORTED_FILETYPES);
dialog.setFilterNames(SUPPORTED_FILETYPES);
dialog.setFileName("*.sql");
String path = dialog.open();
if (path == null) {
return false;
}
file = new File(path);
}
// Save it
saveToFile(file);
// Update the editor input
input = new SQLEditorInput(file);
setInput(input);
setPartName(input.getName());
setTitleToolTip(input.getToolTipText());
} catch (IOException e) {
SQLExplorerPlugin.error("Couldn't save sql", e);
MessageDialog.openError(getSite().getShell(), Messages.getString("SQLEditor.SaveAsDialog.Error"), e.getMessage());
return false;
}
}
setIsDirty(textEditor.isDirty());
return true;
}
/**
* Loads a file into the editor
*
* @param file file to load
*/
/*
* private void loadFromFile(File file) throws IOException { BufferedReader reader = null;
*
* StringBuffer all = new StringBuffer((int)file.length()); String str = null; //String delimiter =
* _editor.getSqlTextViewer().getTextWidget().getLineDelimiter(); // Note: I have changed the delimiter to a
* hardcoded \n because this a) allows the // interface to SQLEditor to be cleaner (see SQLEditor for refactoring
* description) // and I can find several other places where text will be passed to the same text // editor and \n
* is hard coded. If there is an issue with how the view encodes // line delimiters, it is likely to be a global
* problem and we should handle it in // SQLEditor.setText() instead. // reader = new BufferedReader(new
* FileReader(file)); try { while ((str = reader.readLine()) != null) { all.append(str); all.append('\n'); }
*
* setText(all.toString()); isDirty = false; } finally { reader.close(); } }
*/
/**
* Saves the text to a file on the filing system - IE outside of any projects
*/
private void saveToFile(File file) throws IOException {
if (file.exists()) {
file.delete();
}
file.createNewFile();
String content = textEditor.sqlTextViewer.getDocument().get();
// MOD sizhaoliu 2012-04-02 for TDQ-5070 Encoding issue with saving generated sql query action
// BufferedWriter writer = new BufferedWriter(new FileWriter(file));
// writer.write(content, 0, content.length());
// writer.close();
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes(Charset.forName("UTF-8")));
fos.close();
// PTODO rli fixed feature 5186: synchronized the resource.
IFile[] findFile = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(file.toURI());
if (findFile == null) {
return;
}
try {
findFile[0].getParent().refreshLocal(IResource.DEPTH_ONE, null);
} catch (CoreException e) {
e.printStackTrace();
}
}
/**
* Adds a message to the message window
*
* @param message
*/
public void addMessage(Message message) {
if (isClosed()) {
return;
}
// Don't log success messages unless we're supposed to
if (message.getStatus() == Message.Status.SUCCESS
&& !SQLExplorerPlugin.getDefault().getPreferenceStore().getBoolean(IConstants.LOG_SUCCESS_MESSAGES)) {
return;
}
TableItem tableRow = new TableItem(messagesTable, SWT.NONE);
tableRow.setText(message.getTableText());
tableRow.setData(message);
TableColumn[] cols = messagesTable.getColumns();
for (TableColumn col : cols) {
col.pack();
}
}
/**
* Allocates a new tab for an AbstractSQLExecution; the tab and it's contents are completely undefined
*
* @param sqlExec
* @return
*/
public ResultsTab createResultsTab(AbstractSQLExecution job) {
if (tabFolder.isDisposed()) {
return null;
}
// Create the new tab, make it second to last (IE keep the messages tab
// always at the end) and set the new tab's title to the 1-based index
CTabItem tabItem = new CTabItem(tabFolder, SWT.CLOSE, tabFolder.getItems().length - 1);
tabItem.setText(Integer.toString(tabFolder.getItems().length - 1));
tabFolder.setSelection(tabItem);
// Make sure we can track the execution
tabItem.setData(job);
// Create a composite to add all controls to
Composite composite = new Composite(tabFolder, SWT.NONE);
tabItem.setControl(composite);
// Make sure we're visible
getSite().getPage().bringToTop(this);
return new ResultsTab(this, tabItem, composite);
}
public boolean isClosed() {
return tabFolder.isDisposed();
}
/**
* Called internally when the user tries to close a tab
*
* @param tabItem
* @return true if the tab should be closed
*/
private boolean onCloseTab(CTabItem tabItem) {
// Cannot close the messages tab
if (tabItem == messagesTab) {
return false;
}
// The SQL query runs in a background thread and if the tab item gets disposed of
// BEFORE the query has finished we need to notify the thread that there is no UI
// left and that the thread should terminate as soon as possible.
synchronized (this) {
// Get the SQL execution
AbstractSQLExecution job = getSqlExecution(tabItem);
// Stop the statement - but allow it to happen asynchronously
if (job != null) {
tabItem.setData(null);
job.cancel();
}
}
return true;
}
/**
* Called internally when the user tries to close the editor
*/
private void onCloseEditor() {
textEditor.getDocumentProvider().disconnect(getEditorInput());
textEditor.setInput(null);
clearResults();
// ADD msjian TDQ-5952: we should close connections always
// when close the SQLEditor, close the connection
if (session != null) {
List<SQLConnection> connections = session.getUser().getUnusedConnections();
for (SQLConnection sqlConn : connections) {
session.getUser().releaseFromPool(sqlConn);
}
}
// TDQ-5952~
}
/**
* Closes all result tabs and signals all associated AbstractSQLExecutions to stop (if they're still running)
*/
public void clearResults() {
if (tabFolder.isDisposed()) {
return;
}
synchronized (this) {
CTabItem[] tabItems = tabFolder.getItems();
for (CTabItem tabItem : tabItems) {
if (tabItem != messagesTab) {
closeTab(tabItem);
}
}
messagesTable.removeAll();
}
}
/**
* Removes a tab from the results tabs, stopping any pending executions etc
*
* @param tab
*/
private void closeTab(CTabItem tab) {
tab.dispose();
}
/**
* Converts a TabItem into the AbstractSQLExecution for that tab
*
* @param tab
* @return
*/
public AbstractSQLExecution getSqlExecution(CTabItem tab) {
return (AbstractSQLExecution) tab.getData();
}
/**
* Converts an AbstractSQLExecution into its TabItem
*
* @param sqlExec
* @return
*/
public CTabItem getResultsTab(AbstractSQLExecution sqlExec) {
CTabItem[] items = tabFolder.getItems();
for (CTabItem item : items) {
if (item.getData() == sqlExec) {
return item;
}
}
return null;
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.ui.part.EditorPart#isDirty()
*/
@Override
public boolean isDirty() {
boolean saveOnClose = SQLExplorerPlugin.getDefault().getPreferenceStore()
.getBoolean(IConstants.REQUIRE_SAVE_ON_CLOSE_EDITOR);
return saveOnClose && (isDirty || textEditor.isDirty());
}
protected void setIsDirty(boolean isDirty) {
if (this.isDirty != isDirty) {
this.isDirty = isDirty;
firePropertyChange(PROP_DIRTY);
}
if (!isDirty) {
isUntitled = false;
}
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.ui.part.WorkbenchPart#setFocus()
*/
@Override
public void setFocus() {
textEditor.setFocus();
}
/*
* (non-JavaDoc)
*
* @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed()
*/
@Override
public boolean isSaveAsAllowed() {
return textEditor.isSaveAsAllowed();
}
/**
* Sets the message displayed on the Eclipse main status bar
*
* @param s
*/
public void setMessage(String s) {
IStatusLineManager manager = getEditorSite().getActionBars().getStatusLineManager();
manager.setMessage(s);
// statusMgr.setMessage(s);
}
/**
* Sets the dictionary used by the text editor
*
* @param dictionary
*/
public void setNewDictionary(final Dictionary dictionary) {
textEditor.setNewDictionary(dictionary);
}
/**
* Sets the session to use for executing queries
*
* @param session The new Session
*/
@Override
public void setSession(Session session) {
if (session == this.session) {
return;
}
// If we already have a session and we're changing to a different one, close the current one
if (getSession() != null && session != this.session) {
this.session.close();
}
this.session = session;
if (textEditor != null) {
textEditor.onEditorSessionChanged(session);
}
if (toolBar != null) {
toolBar.onEditorSessionChanged(session);
}
}
/**
* @return the session
*/
@Override
public Session getSession() {
// In theory, if our session is somehow closed by something else then we will have already
// had our session changed or reset; however, just in case this doesn't happen we can
// detect it because the session has it's user set to null when it is detached. If that
// happened, then we reset the session to null
if (session != null && session.getUser() == null) {
session = null;
}
return session;
}
/**
* Sets the text of the editor
*
* @param txt
*/
public void setText(String txt) {
IDocument dc = new Document(txt);
textEditor.sqlTextViewer.setDocument(dc);
textEditor.sqlTextViewer.refresh();
}
/**
* Returns the text to be executed; this is the entire text if there is no selection, else just the selected text
*
* @return
*/
public String getSQLToBeExecuted() {
String sql = textEditor.sqlTextViewer.getTextWidget().getSelectionText();
if (sql == null || sql.trim().length() == 0) {
sql = textEditor.sqlTextViewer.getTextWidget().getText();
}
// Normalise this to have standard \n in strings. \r confuses Oracle and
// isn't normally needed internally anyway
StringBuffer sb = new StringBuffer(sql);
for (int i = 0; i < sb.length(); i++) {
if (sb.charAt(i) == '\r') {
sb.deleteCharAt(i);
i--;
}
}
sql = sb.toString();
return sql != null ? sql : "";
}
/**
* returns the line number that the SQL starts on
*
* @return
*/
public int getSQLLineNumber() {
String sql = textEditor.sqlTextViewer.getTextWidget().getSelectionText();
if (sql == null || sql.trim().length() == 0) {
return 1;
}
Point pt = textEditor.sqlTextViewer.getTextWidget().getSelection();
if (pt == null) {
return 1;
}
StyledText text = (StyledText) textEditor.getAdapter(org.eclipse.swt.widgets.Control.class);
int offset = pt.x;
int lineNo = text.getLineAtOffset(offset);
return lineNo + 1;
}
/**
* Clears the text of the editor
*/
public void clearText() {
textEditor.sqlTextViewer.clearText();
}
/**
* Returns the toolbar
*
* @return
*/
public SQLEditorToolBar getEditorToolBar() {
return toolBar;
}
/**
* Returns whether to limit the results and if so by how much.
*
* @return the maximum number of rows to retrieve, 0 for unlimited, or null if it cannot be interpretted
*/
public Integer getLimitResults() {
return toolBar.getLimitResults();
}
/**
* Updates the cursor position info in the status bar
*/
public void updateCursorPosition() {
Object adapter = textEditor.getAdapter(org.eclipse.swt.widgets.Control.class);
if (adapter instanceof StyledText) {
StyledText text = (StyledText) adapter;
int offset = text.getCaretOffset();
int lineNo = text.getLineAtOffset(offset);
int lineOffset = text.getOffsetAtLine(lineNo);
int charNo = offset - lineOffset;
IStatusLineManager manager = getEditorSite().getActionBars().getStatusLineManager();
IContributionItem items[] = manager.getItems();
for (IContributionItem item : items) {
if (item instanceof CursorPositionContrib) {
CursorPositionContrib contrib = (CursorPositionContrib) item;
contrib.setPosition(lineNo + 1, charNo + 1);
break;
}
}
}
}
/**
* Moves the text cursor to a given line and column
*
* @param lineNo
* @param charNo
*/
public void setCursorPosition(int lineNo, int charNo) {
if (lineNo < 1) {
return;
}
if (charNo < 1) {
charNo = 1;
}
Object adapter = textEditor.getAdapter(org.eclipse.swt.widgets.Control.class);
if (adapter instanceof StyledText) {
StyledText text = (StyledText) adapter;
// ADD xqliu 2012-07-24 TDQ-5853 forced to navigate to the first row
lineNo = 1;
// ~ TDQ-5853
int lineOffset = text.getOffsetAtLine(lineNo - 1);
text.setCaretOffset(lineOffset + charNo - 1);
updateCursorPosition();
text.setFocus();
text.showSelection();
}
}
/*
* (non-Javadoc)
*
* @see net.sourceforge.sqlexplorer.plugin.editors.SwitchableSessionEditor#refreshToolbars()
*/
@Override
public void refreshToolbars() {
getEditorToolBar().refresh();
}
/**
* Added TDQ-7532, 20130719 yyin: to lock the editor make it not editable/or editable
*
* @param lock : true - set it editable; false - set it not editable.
*/
public void setEditable(boolean lock) {
this.textEditor.getViewer().setEditable(lock);
this.toolBar.getToolbarControl().setEnabled(lock);
}
}