0001: /*
0002: * DwPanel.java
0003: *
0004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
0005: *
0006: * Copyright 2002-2008, Thomas Kellerer
0007: * No part of this code maybe reused without the permission of the author
0008: *
0009: * To contact the author please send an email to: support@sql-workbench.net
0010: *
0011: */
0012: package workbench.gui.sql;
0013:
0014: import java.awt.BorderLayout;
0015: import java.awt.Component;
0016: import java.awt.Cursor;
0017: import java.awt.EventQueue;
0018: import java.awt.Window;
0019: import java.sql.ResultSet;
0020: import java.sql.SQLException;
0021: import java.util.List;
0022: import javax.swing.BorderFactory;
0023: import javax.swing.CellEditor;
0024: import javax.swing.JOptionPane;
0025: import javax.swing.JOptionPane;
0026: import javax.swing.JOptionPane;
0027: import javax.swing.JPanel;
0028: import javax.swing.JTextField;
0029: import javax.swing.ListSelectionModel;
0030: import javax.swing.SwingConstants;
0031: import javax.swing.SwingUtilities;
0032: import javax.swing.border.Border;
0033: import javax.swing.border.EmptyBorder;
0034: import javax.swing.border.EtchedBorder;
0035: import javax.swing.event.ChangeEvent;
0036: import javax.swing.event.ChangeListener;
0037: import javax.swing.event.ListSelectionEvent;
0038: import javax.swing.event.ListSelectionListener;
0039: import javax.swing.event.TableModelEvent;
0040: import javax.swing.event.TableModelListener;
0041: import javax.swing.table.TableColumn;
0042: import javax.swing.table.TableColumnModel;
0043: import javax.swing.table.TableModel;
0044: import workbench.db.ColumnIdentifier;
0045: import workbench.db.TableIdentifier;
0046: import workbench.db.WbConnection;
0047: import workbench.gui.MainWindow;
0048: import workbench.gui.components.GenericRowMonitor;
0049: import workbench.gui.components.WbTextCellEditor;
0050: import workbench.util.ExceptionUtil;
0051: import workbench.gui.WbSwingUtilities;
0052: import workbench.gui.actions.CopyRowAction;
0053: import workbench.gui.actions.DeleteDependentRowsAction;
0054: import workbench.gui.actions.DeleteRowAction;
0055: import workbench.gui.actions.InsertRowAction;
0056: import workbench.gui.actions.SelectKeyColumnsAction;
0057: import workbench.gui.actions.UpdateDatabaseAction;
0058: import workbench.gui.components.DataStoreTableModel;
0059: import workbench.gui.components.OneLineTableModel;
0060: import workbench.gui.components.TextComponentMouseListener;
0061: import workbench.gui.components.WbScrollPane;
0062: import workbench.gui.components.WbTable;
0063: import workbench.interfaces.DbData;
0064: import workbench.interfaces.DbUpdater;
0065: import workbench.interfaces.Interruptable;
0066: import workbench.interfaces.JobErrorHandler;
0067: import workbench.interfaces.StatementRunner;
0068: import workbench.log.LogMgr;
0069: import workbench.resource.ResourceMgr;
0070: import workbench.resource.Settings;
0071: import workbench.sql.StatementRunnerResult;
0072: import workbench.storage.DataStore;
0073: import workbench.storage.NamedSortDefinition;
0074: import workbench.storage.RowActionMonitor;
0075: import workbench.util.NumberStringCache;
0076: import workbench.util.StringUtil;
0077: import workbench.util.WbThread;
0078:
0079: /**
0080: * A Panel which displays the result of a SELECT statement and
0081: * can save changes to the database
0082: *
0083: * @author support@sql-workbench.net
0084: */
0085: public class DwPanel extends JPanel implements TableModelListener,
0086: ListSelectionListener, ChangeListener, DbData, DbUpdater,
0087: Interruptable, JobErrorHandler {
0088: public static final String PROP_UPDATE_TABLE = "updateTable";
0089:
0090: protected WbTable dataTable;
0091:
0092: protected DwStatusBar statusBar;
0093:
0094: private String sql;
0095: private String lastMessage;
0096: protected WbConnection dbConnection;
0097: private boolean hasResultSet;
0098:
0099: protected WbScrollPane scrollPane;
0100: private long lastExecutionTime = 0;
0101:
0102: private boolean showLoadProgress;
0103: private boolean savingData = false;
0104:
0105: protected UpdateDatabaseAction updateAction;
0106: protected InsertRowAction insertRow;
0107: protected CopyRowAction duplicateRow;
0108: protected DeleteRowAction deleteRow;
0109: protected DeleteDependentRowsAction deleteDependentRow;
0110: protected SelectKeyColumnsAction selectKeys;
0111:
0112: private boolean editingStarted;
0113: private boolean batchUpdate;
0114: private boolean manageUpdateAction;
0115: private boolean readOnly;
0116:
0117: private String[] lastResultMessages;
0118: private StatementRunner stmtRunner;
0119: private GenericRowMonitor genericRowMonitor;
0120: private ReferenceTableNavigator referenceNavigator;
0121:
0122: public DwPanel() {
0123: this (null);
0124: }
0125:
0126: public DwPanel(DwStatusBar aStatusBar) {
0127: JTextField stringField = new JTextField();
0128: stringField.setBorder(WbSwingUtilities.EMPTY_BORDER);
0129: stringField.addMouseListener(new TextComponentMouseListener());
0130:
0131: JTextField numberField = new JTextField();
0132: numberField.setBorder(WbSwingUtilities.EMPTY_BORDER);
0133: numberField.setHorizontalAlignment(SwingConstants.RIGHT);
0134: numberField.addMouseListener(new TextComponentMouseListener());
0135:
0136: this .initLayout(aStatusBar);
0137:
0138: this .setDoubleBuffered(true);
0139: this .dataTable.addTableModelListener(this );
0140:
0141: this .updateAction = new UpdateDatabaseAction(this );
0142: this .insertRow = new InsertRowAction(this );
0143: this .deleteRow = new DeleteRowAction(this );
0144: this .deleteDependentRow = new DeleteDependentRowsAction(this );
0145: this .duplicateRow = new CopyRowAction(this );
0146: this .selectKeys = new SelectKeyColumnsAction(this );
0147:
0148: dataTable.addPopupAction(this .updateAction, true);
0149: dataTable.addPopupAction(this .insertRow, true);
0150: dataTable.addPopupAction(this .deleteRow, false);
0151: dataTable.addPopupAction(this .deleteDependentRow, false);
0152:
0153: this .dataTable
0154: .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
0155: this .dataTable.setRowSelectionAllowed(true);
0156: this .dataTable.getSelectionModel().addListSelectionListener(
0157: this );
0158: this .dataTable.setHighlightRequiredFields(Settings
0159: .getInstance().getHighlightRequiredFields());
0160: this .dataTable.setStatusBar(this .statusBar);
0161: this .genericRowMonitor = new GenericRowMonitor(this .statusBar);
0162: }
0163:
0164: public void initTableNavigation(MainWindow container) {
0165: this .referenceNavigator = new ReferenceTableNavigator(this ,
0166: container);
0167: }
0168:
0169: public SelectKeyColumnsAction getSelectKeysAction() {
0170: return this .selectKeys;
0171: }
0172:
0173: public void checkAndSelectKeyColumns() {
0174: if (checkUpdateTable()) {
0175: dataTable.selectKeyColumns();
0176: }
0177: }
0178:
0179: public void setManageUpdateAction(boolean aFlag) {
0180: this .manageUpdateAction = aFlag;
0181: }
0182:
0183: public void setDefaultStatusMessage(String aMessage) {
0184: this .statusBar.setReadyMsg(aMessage);
0185: }
0186:
0187: public void disconnect() {
0188: this .setConnection(null);
0189: }
0190:
0191: public void setCursor(Cursor newCursor) {
0192: super .setCursor(newCursor);
0193: this .dataTable.setCursor(newCursor);
0194: }
0195:
0196: public void setShowLoadProcess(boolean aFlag) {
0197: this .showLoadProgress = aFlag;
0198: }
0199:
0200: public void setPrintHeader(String header) {
0201: this .dataTable.setPrintHeader(header);
0202: }
0203:
0204: public void dispose() {
0205: this .clearContent();
0206: if (this .stmtRunner != null) {
0207: this .stmtRunner.dispose();
0208: }
0209: this .stmtRunner = null;
0210: }
0211:
0212: /**
0213: * Defines the connection for this DwPanel.
0214: */
0215: public void setConnection(WbConnection aConn) {
0216: this .clearContent();
0217: this .dbConnection = aConn;
0218: if (this .stmtRunner != null) {
0219: this .stmtRunner.done();
0220: }
0221: this .stmtRunner = null;
0222: this .clearStatusMessage();
0223: }
0224:
0225: private void createStatementRunner() {
0226: if (this .stmtRunner == null) {
0227: this .stmtRunner = StatementRunner.Factory.createRunner();
0228: this .stmtRunner.setRowMonitor(this .genericRowMonitor);
0229: }
0230:
0231: if (this .stmtRunner != null) {
0232: this .stmtRunner.setConnection(this .dbConnection);
0233: }
0234: }
0235:
0236: /**
0237: * Sets the handler which performs the update to the database.
0238: *
0239: * This delegate is passed to the UpdateDatabaseAction. The action will in turn
0240: * call the delegate's saveChangesToDatabase() method instead of ours.
0241: *
0242: * @see workbench.interfaces.DbUpdater#saveChangesToDatabase()
0243: * @see #saveChangesToDatabase()
0244: * @see workbench.gui.sql.SqlPanel#saveChangesToDatabase()
0245: */
0246: public void setUpdateHandler(DbUpdater aDelegate) {
0247: this .updateAction.setClient(aDelegate);
0248: }
0249:
0250: /**
0251: * Prepare the DwPanel for saving any changes to the database.
0252: * This will check for the PK columns and if necessary
0253: * ask the user to specify them.
0254: * It will also prompt the user to verify the generated
0255: * update statements.
0256: * If everything is OK, true will be returned
0257: * If the user cancels the PK column selection or the
0258: * statement preview, false will be returned. In that
0259: * case saveChanges() should not be called
0260: */
0261: boolean prepareDatabaseUpdate() {
0262: if (this .dbConnection == null)
0263: return false;
0264: DataStore ds = this .dataTable.getDataStore();
0265: if (ds == null)
0266: return false;
0267:
0268: boolean needPk = ds.needPkForUpdate();
0269: if (needPk) {
0270: boolean hasPk = dataTable.detectDefinedPkColumns();
0271: if (!hasPk) {
0272: hasPk = getTable().selectKeyColumns();
0273: }
0274: if (!hasPk)
0275: return false;
0276: }
0277:
0278: boolean pkComplete = ds.pkColumnsComplete();
0279: if (needPk && !pkComplete) {
0280: List<ColumnIdentifier> columns = ds.getMissingPkColumns();
0281: MissingPkDialog dialog = new MissingPkDialog(columns);
0282: boolean ok = dialog.checkContinue(this );
0283: if (!ok)
0284: return false;
0285: }
0286:
0287: // check if we really want to save the currentData
0288: // it fhe SQL Preview is not enabled this will
0289: // always return true, otherwise it depends on the user's
0290: // selection after the SQL preview has been displayed
0291: if (!this .shouldSaveChanges(this .dbConnection))
0292: return false;
0293: return true;
0294: }
0295:
0296: /**
0297: * Starts the saving of the data in the background
0298: */
0299: public void saveChangesToDatabase() {
0300: if (savingData) {
0301: Exception e = new IllegalStateException(
0302: "Concurrent save called");
0303: LogMgr.logWarning("DwPanel.saveChangesToDatase()",
0304: "Save changes called while save in progress", e);
0305: return;
0306: }
0307:
0308: if (!this .prepareDatabaseUpdate())
0309: return;
0310:
0311: final JobErrorHandler handler = this ;
0312: WbThread t = new WbThread("DwPanel update") {
0313: public void run() {
0314: try {
0315: saveChanges(dbConnection, handler);
0316: } catch (Exception e) {
0317: // Exception have already been displayed to the user --> Log only
0318: LogMgr.logError("DwPanel.doSave()",
0319: "Error saving data", e);
0320: }
0321: }
0322: };
0323: t.start();
0324:
0325: }
0326:
0327: public boolean shouldSaveChanges(WbConnection aConnection) {
0328: if (!Settings.getInstance().getPreviewDml()
0329: && !this .dbConnection.getProfile().isConfirmUpdates())
0330: return true;
0331:
0332: this .dataTable.stopEditing();
0333: DataStore ds = this .dataTable.getDataStore();
0334: DwUpdatePreview preview = new DwUpdatePreview();
0335:
0336: boolean doSave = preview.confirmUpdate(this , ds, dbConnection);
0337:
0338: return doSave;
0339: }
0340:
0341: public int saveChanges(WbConnection aConnection,
0342: JobErrorHandler errorHandler) throws SQLException {
0343: int rows = 0;
0344:
0345: synchronized (this ) {
0346: this .dataTable.stopEditing();
0347: if (this .manageUpdateAction) {
0348: this .disableUpdateActions();
0349: }
0350:
0351: try {
0352: setStatusMessage(ResourceMgr
0353: .getString("MsgUpdatingDatabase"));
0354: savingData = true;
0355: DataStore ds = this .dataTable.getDataStore();
0356: long start, end;
0357: ds.setProgressMonitor(this .genericRowMonitor);
0358: start = System.currentTimeMillis();
0359: rows = ds.updateDb(aConnection, errorHandler);
0360: end = System.currentTimeMillis();
0361: ds.setProgressMonitor(null);
0362: long sqlTime = (end - start);
0363: this .lastMessage = ResourceMgr
0364: .getString("MsgUpdateSuccessfull");
0365: this .lastMessage = this .lastMessage + "\n" + rows + " "
0366: + ResourceMgr.getString("MsgRowsAffected")
0367: + "\n";
0368: this .lastMessage = this .lastMessage
0369: + ResourceMgr.getString("MsgExecTime") + " "
0370: + (((double) sqlTime) / 1000.0) + "s";
0371: if (!ds.lastUpdateHadErrors()) {
0372: endEdit();
0373: }
0374: } catch (SQLException e) {
0375: this .lastMessage = ExceptionUtil.getDisplay(e);
0376: rows = -1;
0377: throw e;
0378: } finally {
0379: savingData = false;
0380: this .clearStatusMessage();
0381: if (this .manageUpdateAction)
0382: this .checkResultSetActions();
0383: }
0384: }
0385:
0386: return rows;
0387: }
0388:
0389: protected void disableUpdateActions() {
0390: this .updateAction.setEnabled(false);
0391: this .insertRow.setEnabled(false);
0392: this .duplicateRow.setEnabled(false);
0393: this .deleteRow.setEnabled(false);
0394: this .deleteDependentRow.setEnabled(false);
0395: }
0396:
0397: /**
0398: * Pass the table to be used for future updates to the underlying
0399: * DataStore.
0400: * This will also reset the internal cache of the ReferenceTableNavigator.
0401: *
0402: * @see workbench.gui.sql.ReferenceTableNavigator#reset()
0403: * @see workbench.storage.DataStore#setUpdateTableToBeUsed(workbench.db.TableIdentifier)
0404: */
0405: public void setUpdateTableToBeUsed(TableIdentifier table) {
0406: DataStore ds = this .dataTable.getDataStore();
0407: if (ds != null) {
0408: ds.setUpdateTableToBeUsed(table);
0409: }
0410: referenceNavigator.reset();
0411: }
0412:
0413: /**
0414: * Define the update table to be used.
0415: * @see workbench.storage.DataStore#setUpdateTable(TableIdentifier)
0416: */
0417: public void setUpdateTable(TableIdentifier table) {
0418: this .setStatusMessage(ResourceMgr
0419: .getString("MsgRetrieveUpdateTableInfo"));
0420: try {
0421: DataStore ds = this .dataTable.getDataStore();
0422: if (ds != null) {
0423: ds.setUpdateTable(table);
0424: }
0425: checkResultSetActions();
0426: this .fireUpdateTableChanged();
0427: } finally {
0428: this .clearStatusMessage();
0429: }
0430: }
0431:
0432: private void fireUpdateTableChanged() {
0433: TableIdentifier table = null;
0434:
0435: if (this .getTable() != null) {
0436: DataStore ds = this .getTable().getDataStore();
0437: if (ds != null)
0438: table = ds.getUpdateTable();
0439: }
0440: if (table != null)
0441: firePropertyChange(PROP_UPDATE_TABLE, null, table
0442: .getTableExpression());
0443: }
0444:
0445: public void setReadOnly(boolean aFlag) {
0446: this .readOnly = aFlag;
0447: if (this .readOnly && this .editingStarted) {
0448: this .endEdit();
0449: this .disableUpdateActions();
0450: this .dataTable.restoreOriginalValues();
0451: } else {
0452: this .insertRow.setEnabled(true);
0453: }
0454: }
0455:
0456: public boolean isReadOnly() {
0457: return this .readOnly;
0458: }
0459:
0460: public boolean checkUpdateTable() {
0461: if (this .readOnly)
0462: return false;
0463:
0464: setStatusMessage(ResourceMgr
0465: .getString("MsgCheckingUpdateTable"));
0466: statusBar.forcePaint();
0467:
0468: boolean result = false;
0469: try {
0470: DataStore ds = this .dataTable.getDataStore();
0471: if (ds == null)
0472: return false;
0473: if (this .dbConnection == null)
0474: return false;
0475: if (this .sql == null)
0476: return false;
0477: result = ds.checkUpdateTable(this .dbConnection);
0478:
0479: if (!result) {
0480: TableIdentifier tbl = dataTable.selectUpdateTable();
0481: if (tbl != null) {
0482: this .setUpdateTable(tbl);
0483: result = true;
0484: }
0485: }
0486:
0487: if (result) {
0488: this .fireUpdateTableChanged();
0489: }
0490:
0491: this .selectKeys.setEnabled(result);
0492: } finally {
0493: this .clearStatusMessage();
0494: }
0495: return result;
0496: }
0497:
0498: public boolean hasKeyColumns() {
0499: if (this .dataTable.getDataStore() == null)
0500: return false;
0501: return this .dataTable.getDataStore().hasPkColumns();
0502: }
0503:
0504: public boolean isUpdateable() {
0505: if (this .dataTable.getDataStore() == null)
0506: return false;
0507: return this .dataTable.getDataStore().isUpdateable();
0508: }
0509:
0510: public boolean hasUpdateableColumns() {
0511: if (this .dataTable.getDataStore() == null)
0512: return false;
0513: return this .dataTable.getDataStore().hasUpdateableColumns();
0514: }
0515:
0516: public int getQueryTimeout() {
0517: return this .statusBar.getQueryTimeout();
0518: }
0519:
0520: public void setQueryTimeout(int value) {
0521: this .statusBar.setQueryTimeout(value);
0522: }
0523:
0524: public int getMaxRows() {
0525: return this .statusBar.getMaxRows();
0526: }
0527:
0528: public void setMaxRows(int aMax) {
0529: this .statusBar.setMaxRows(aMax);
0530: }
0531:
0532: /**
0533: * Displays the last execution time in the status bar
0534: */
0535: public void showlastExecutionTime() {
0536: statusBar.setExecutionTime(lastExecutionTime);
0537: }
0538:
0539: public void setSortDefinition(NamedSortDefinition sort) {
0540: DataStoreTableModel model = this .dataTable
0541: .getDataStoreTableModel();
0542: if (model != null) {
0543: model.setSortDefinition(sort);
0544: }
0545: }
0546:
0547: public NamedSortDefinition getCurrentSort() {
0548: NamedSortDefinition currentSort = null;
0549: DataStoreTableModel model = this .dataTable
0550: .getDataStoreTableModel();
0551: if (model != null) {
0552: currentSort = model.getSortDefinition();
0553: }
0554: return currentSort;
0555: }
0556:
0557: /**
0558: * Execute the given SQL statement and display the result.
0559: */
0560: public boolean runQuery(String aSql, boolean respectMaxRows)
0561: throws SQLException, Exception {
0562: if (this .stmtRunner == null)
0563: this .createStatementRunner();
0564:
0565: boolean success = false;
0566:
0567: try {
0568: this .clearContent();
0569:
0570: this .sql = aSql;
0571: int max = (respectMaxRows ? this .statusBar.getMaxRows() : 0);
0572: int timeout = this .statusBar.getQueryTimeout();
0573:
0574: this .stmtRunner.runStatement(aSql, max, timeout);
0575: StatementRunnerResult result = this .stmtRunner.getResult();
0576:
0577: if (result != null) {
0578: if (result.isSuccess()) {
0579: success = true;
0580: this .showData(result);
0581: } else {
0582: String err = result.getMessageBuffer().toString();
0583: showError(err);
0584: WbSwingUtilities.showErrorMessage(SwingUtilities
0585: .getWindowAncestor(this ), err);
0586: }
0587: }
0588: } finally {
0589: this .stmtRunner.done();
0590: this .clearStatusMessage();
0591: }
0592: return success;
0593: }
0594:
0595: /**
0596: * Display any result set that is contained in the StatementRunnerResult.
0597: * @param result the result from the {@link workbench.interfaces.StatementRunner} to be displayed
0598: *
0599: * @see workbench.sql.DefaultStatementRunner
0600: */
0601: public void showData(final StatementRunnerResult result)
0602: throws SQLException {
0603: if (result == null || !result.isSuccess()) {
0604: this .lastExecutionTime = 0;
0605: this .hasResultSet = false;
0606: } else {
0607: this .lastExecutionTime = result.getExecutionTime();
0608:
0609: if (result.hasDataStores()) {
0610: showData(result.getDataStores().get(0), result
0611: .getSourceCommand());
0612: } else if (result.hasResultSets()) {
0613: showData(result.getResultSets().get(0), result
0614: .getSourceCommand());
0615: }
0616: }
0617: }
0618:
0619: public void showData(final ResultSet result, final String sql)
0620: throws SQLException {
0621: DataStore newData = null;
0622:
0623: // passing the maxrows to the datastore is a workaround for JDBC drivers
0624: // which do not support the setMaxRows() method.
0625: // The datastore will make sure that not more rows are read than really requested
0626: if (this .showLoadProgress) {
0627: newData = new DataStore(result, true,
0628: this .genericRowMonitor, this .getMaxRows(),
0629: this .dbConnection);
0630: } else {
0631: newData = new DataStore(result, true, null, this
0632: .getMaxRows(), this .dbConnection);
0633: }
0634: showData(newData, sql);
0635: }
0636:
0637: public void showData(final DataStore newData, final String statement)
0638: throws SQLException {
0639: try {
0640: this .setBatchUpdate(true);
0641: this .hasResultSet = true;
0642: this .sql = statement;
0643:
0644: newData.setOriginalConnection(this .dbConnection);
0645: newData.setProgressMonitor(null);
0646: this .clearStatusMessage();
0647:
0648: WbSwingUtilities.invoke(new Runnable() {
0649: public void run() {
0650: dataTable.reset();
0651: dataTable.setAutoCreateColumnsFromModel(true);
0652: dataTable.setModel(
0653: new DataStoreTableModel(newData), true);
0654: dataTable.adjustOrOptimizeColumns();
0655: //StringBuilder header = new StringBuilder(80);
0656: //header.append(ResourceMgr.getString("TxtPrintHeaderResultFrom"));
0657: //header.append(sql);
0658: setPrintHeader(sql);
0659: dataTable.checkCopyActions();
0660: checkResultSetActions();
0661: }
0662: });
0663:
0664: } finally {
0665: setBatchUpdate(false);
0666: }
0667: }
0668:
0669: private void checkResultSetActions() {
0670: boolean hasResult = this .hasResultSet();
0671: int rows = this .getTable().getSelectedRowCount();
0672: this .dataTable.getExportAction().setEnabled(hasResult);
0673: if (this .updateAction != null)
0674: this .updateAction.setEnabled(isModified());
0675: if (this .duplicateRow != null)
0676: this .duplicateRow.setEnabled(rows == 1);
0677: if (this .deleteRow != null)
0678: this .deleteRow.setEnabled(rows > 0);
0679: if (this .deleteDependentRow != null)
0680: this .deleteDependentRow.setEnabled(rows > 0);
0681: if (this .insertRow != null)
0682: this .insertRow.setEnabled(hasResult);
0683: if (this .selectKeys != null)
0684: this .selectKeys.setEnabled(hasResult);
0685: this .dataTable.checkCopyActions();
0686: }
0687:
0688: /**
0689: * This method will update the row info display on the statusbar.
0690: */
0691: public void rowCountChanged() {
0692: int startRow = 0;
0693: int endRow = 0;
0694: int count = 0;
0695: startRow = this .dataTable.getFirstVisibleRow();
0696: endRow = this .dataTable.getLastVisibleRow(startRow);
0697: count = this .dataTable.getRowCount();
0698: statusBar.setRowcount(startRow + 1, endRow + 1, count);
0699: }
0700:
0701: public int duplicateRow() {
0702: if (this .dataTable.getSelectedRowCount() != 1)
0703: return -1;
0704: int row = this .dataTable.getSelectedRow();
0705: if (row < 0)
0706: return -1;
0707:
0708: if (this .readOnly)
0709: return -1;
0710: if (!this .startEdit(false))
0711: return -1;
0712: final int newRow = this .dataTable.duplicateRow(row);
0713:
0714: if (newRow >= 0) {
0715: // Make the new row the current row
0716: // and start editing in the first column
0717: EventQueue.invokeLater(new Runnable() {
0718: public void run() {
0719: dataTable.getSelectionModel().setSelectionInterval(
0720: newRow, newRow);
0721: dataTable.setEditingRow(newRow);
0722: dataTable.setEditingColumn(1);
0723: dataTable.editCellAt(newRow, 1);
0724: CellEditor edit = dataTable
0725: .getCellEditor(newRow, 1);
0726: if (edit instanceof WbTextCellEditor) {
0727: ((WbTextCellEditor) edit).requestFocus();
0728: }
0729: rowCountChanged();
0730: }
0731: });
0732: }
0733:
0734: return newRow;
0735: }
0736:
0737: public void deleteRowWithDependencies() {
0738: if (this .readOnly)
0739: return;
0740:
0741: if (!this .startEdit(true))
0742: return;
0743:
0744: setStatusMessage(ResourceMgr.getString("MsgCalcDependencies"));
0745: final Component c = this ;
0746: WbSwingUtilities.showWaitCursor(this );
0747:
0748: // As checking all dependent tables can potentially
0749: // take some time (especially with Oracle...)
0750: // I'm starting a new thread to do the work
0751: // And I cannot re-use the deleteRow() function as
0752: // that wouldn't allow me to change the status message
0753: // to indicate the dependency checking is running
0754: // startEdit() is also changing the status message
0755: // and would be executed after a local change here
0756: // if I reused deleteRow()
0757: Thread t = new WbThread("DeleteDependency") {
0758: public void run() {
0759: try {
0760: dbConnection.setBusy(true);
0761: dataTable.deleteRow(true);
0762: rowCountChanged();
0763: } catch (SQLException e) {
0764: LogMgr.logError("DwPanel.deleteRow()",
0765: "Error deleting row from table", e);
0766: WbSwingUtilities.showErrorMessage(ExceptionUtil
0767: .getDisplay(e));
0768: } finally {
0769: dbConnection.setBusy(false);
0770: clearStatusMessage();
0771: WbSwingUtilities.showDefaultCursor(c);
0772: }
0773: }
0774: };
0775: t.start();
0776: }
0777:
0778: public void deleteRow() {
0779: if (this .readOnly)
0780: return;
0781: if (!this .startEdit(true))
0782: return;
0783: try {
0784: this .dataTable.deleteRow(false);
0785: this .rowCountChanged();
0786: } catch (SQLException e) {
0787: LogMgr.logError("DwPanel.deleteRow()",
0788: "Error deleting row from table", e);
0789: WbSwingUtilities.showErrorMessage(ExceptionUtil
0790: .getDisplay(e));
0791: }
0792: }
0793:
0794: public long addRow() {
0795: if (this .readOnly)
0796: return -1;
0797: if (!this .startEdit())
0798: return -1;
0799: long newRow = this .dataTable.addRow();
0800: if (newRow > -1)
0801: this .rowCountChanged();
0802: return newRow;
0803: }
0804:
0805: public boolean confirmCancel() {
0806: return true;
0807: }
0808:
0809: public void cancelExecution() {
0810: if (this .stmtRunner != null) {
0811: this .stmtRunner.cancel();
0812: }
0813: }
0814:
0815: public String getLastMessage() {
0816: if (this .lastResultMessages != null) {
0817: StringBuilder msg = new StringBuilder(
0818: lastResultMessages.length * 80);
0819: for (int i = 0; i < lastResultMessages.length; i++) {
0820: msg.append(lastResultMessages[i]);
0821: msg.append('\n');
0822: }
0823: this .lastMessage = msg.toString();
0824: this .lastResultMessages = null;
0825: }
0826:
0827: if (this .lastMessage == null)
0828: this .lastMessage = "";
0829:
0830: return this .lastMessage;
0831: }
0832:
0833: public boolean hasResultSet() {
0834: return this .hasResultSet;
0835: }
0836:
0837: /**
0838: * Returns true if the DataStore of the Table has been modified.
0839: * @see workbench.storage.DataStore#isModified()
0840: */
0841: public boolean isModified() {
0842: DataStore ds = this .dataTable.getDataStore();
0843: if (ds == null)
0844: return false;
0845: else
0846: return ds.isModified();
0847: }
0848:
0849: private void initLayout(DwStatusBar status) {
0850: this .setLayout(new BorderLayout());
0851: this .setBorder(WbSwingUtilities.EMPTY_BORDER);
0852: this .dataTable = new WbTable(true, true, true);
0853: this .dataTable.setRowResizeAllowed(Settings.getInstance()
0854: .getAllowRowHeightResizing());
0855: if (status != null) {
0856: this .statusBar = status;
0857: } else {
0858: this .statusBar = new DwStatusBar();
0859: Border b = BorderFactory.createCompoundBorder(
0860: new EmptyBorder(2, 0, 0, 0), new EtchedBorder());
0861: this .statusBar.setBorder(b);
0862: this .add(this .statusBar, BorderLayout.SOUTH);
0863: }
0864:
0865: this .statusBar.setFocusable(false);
0866: this .setFocusable(false);
0867: this .scrollPane = new WbScrollPane(this .dataTable);
0868: this .scrollPane.getViewport().addChangeListener(this );
0869:
0870: this .add(this .scrollPane, BorderLayout.CENTER);
0871: this .dataTable.setBorder(WbSwingUtilities.EMPTY_BORDER);
0872: this .dataTable.setAdjustToColumnLabel(true);
0873: }
0874:
0875: public void updateStatusBar() {
0876: this .rowCountChanged();
0877: }
0878:
0879: /**
0880: * Show a message in the status panel.
0881: *
0882: * @see DwStatusBar#setStatusMessage(String)
0883: */
0884: public void setStatusMessage(final String aMsg) {
0885: this .statusBar.setStatusMessage(aMsg);
0886: }
0887:
0888: /**
0889: * Clears the display on the status bar.
0890: *
0891: * @see DwStatusBar#clearStatusMessage()
0892: */
0893: public void clearStatusMessage() {
0894: this .statusBar.clearStatusMessage();
0895: }
0896:
0897: public void showError(String error) {
0898: this .setMessageDisplayModel(this .getErrorTableModel(error));
0899: }
0900:
0901: protected void setMessageDisplayModel(final TableModel aModel) {
0902: if (this .dataTable.getModel() == aModel)
0903: return;
0904: WbSwingUtilities.invoke(new Runnable() {
0905: public void run() {
0906: dataTable.setModel(aModel);
0907: TableColumnModel colMod = dataTable.getColumnModel();
0908: TableColumn col = colMod.getColumn(0);
0909: col.setPreferredWidth(getWidth() - 10);
0910: statusBar.setRowcount(0, 0, 0);
0911: }
0912: });
0913: }
0914:
0915: /**
0916: * Clears everything.
0917: * <ul>
0918: * <li>Ends the edit mode</li>
0919: * <li>removes all rows from the table</li>
0920: * <li>sets the hasResultSet flag to false</li>
0921: * <li>the lastMessage is set to an empty string</li>
0922: * <li>the last SQL is set to null</li>
0923: * <li>the statusbar display is cleared</li>
0924: * </ul>
0925: */
0926: public void clearContent() {
0927: this .endEdit();
0928: this .dataTable.reset();
0929: this .hasResultSet = false;
0930: this .lastMessage = null;
0931: this .sql = null;
0932: this .statusBar.clearRowcount();
0933: this .statusBar.clearExecutionTime();
0934: this .selectKeys.setEnabled(false);
0935: checkResultSetActions();
0936: }
0937:
0938: public int getActionOnError(int errorRow, String errorColumn,
0939: String data, String errorMessage) {
0940: String msg = ResourceMgr.getString("ErrUpdateSqlError");
0941: try {
0942: String d = "";
0943: if (data != null) {
0944: if (data.length() > 50)
0945: d = data.substring(0, 50) + "...";
0946: else
0947: d = data;
0948: }
0949:
0950: msg = StringUtil.replace(msg, "%statement%", d);
0951: msg = StringUtil.replace(msg, "%message%", errorMessage);
0952:
0953: String r = "";
0954: if (errorRow > -1) {
0955: r = ResourceMgr.getString("TxtErrorRow").replaceAll(
0956: "%row%",
0957: NumberStringCache.getNumberString(errorRow));
0958: }
0959: msg = StringUtil.replace(msg, "%row%", r);
0960: } catch (Exception e) {
0961: LogMgr.logError("DwPanel.getActionOnError()",
0962: "Error while building error message", e);
0963: msg = "An error occurred during update: \n" + errorMessage;
0964: }
0965:
0966: Window w = SwingUtilities.getWindowAncestor(this );
0967: int choice = WbSwingUtilities.getYesNoIgnoreAll(w, msg);
0968:
0969: int result = JobErrorHandler.JOB_ABORT;
0970:
0971: if (choice == JOptionPane.YES_OPTION) {
0972: result = JobErrorHandler.JOB_CONTINUE;
0973: } else if (choice == WbSwingUtilities.IGNORE_ALL) {
0974: result = JobErrorHandler.JOB_IGNORE_ALL;
0975: }
0976: return result;
0977: }
0978:
0979: /**
0980: * Returns a TableModel which displays an error text.
0981: * This is used to show a hint in the table panel that an error
0982: * occurred and the actual error message is displayed in the log
0983: * panel
0984: */
0985: private TableModel getErrorTableModel(String aMsg) {
0986: String title = ResourceMgr.getString("ErrMessageTitle");
0987: OneLineTableModel errorMessageModel = new OneLineTableModel(
0988: title, aMsg);
0989: return errorMessageModel;
0990: }
0991:
0992: public WbTable getTable() {
0993: return this .dataTable;
0994: }
0995:
0996: /**
0997: * Stops the editing mode of the displayed WbTable:
0998: * <ul>
0999: * <li>the status column is turned off</li>
1000: * <li>the edit actions are enabled/disabled correctly </li>
1001: * <li>the originalValues for the DataStore are restored </li>
1002: * </ul>
1003: */
1004: public void endEdit() {
1005: endEdit(true);
1006: }
1007:
1008: public void endEdit(boolean restoreData) {
1009: if (!this .editingStarted)
1010: return;
1011: this .editingStarted = false;
1012: this .dataTable.stopEditing();
1013: this .dataTable.setShowStatusColumn(false);
1014: this .checkResultSetActions();
1015: this .updateAction.setEnabled(false);
1016: if (restoreData)
1017: this .dataTable.restoreOriginalValues();
1018: }
1019:
1020: public boolean startEdit() {
1021: return startEdit(true);
1022: }
1023:
1024: /**
1025: * Starts the "edit" mode of the table. It will not start the edit
1026: * mode, if the table is "read only" meaning if no update table (=database
1027: * table) is defined.
1028: * The following actions are carried out:
1029: * <ul>
1030: * <li>if the updateable flag is not yet set, try to find out which table to update</li>
1031: * <li>the status column is displayec</li>
1032: * <li>the corresponding actions (insert row, delete row) are enabled</li>
1033: * <li>the startEdit action is turned to "switched on"</li>
1034: * </ul>
1035: * @param restoreSelection if true the selected rows before starting the edit mode are restored
1036: */
1037: public boolean startEdit(boolean restoreSelection) {
1038: if (this .readOnly)
1039: return false;
1040:
1041: this .editingStarted = false;
1042:
1043: final int[] selectedRows = this .dataTable.getSelectedRows();
1044: final int currentRow = this .dataTable.getEditingRow();
1045: final int currentColumn = this .dataTable.getEditingColumn();
1046:
1047: Window w = SwingUtilities.getWindowAncestor(this );
1048:
1049: // if the result is not yet updateable (automagically)
1050: // then try to find the table. If the table cannot be
1051: // determined, then ask the user
1052: boolean updateTablePresent = this .checkUpdateTable();
1053: boolean updateable = this .isUpdateable();
1054:
1055: if (!updateable && !updateTablePresent) {
1056: // checkUpdateTable() will have taken every attempt to find an update table
1057: // including asking the user to select a table from a multi-table result set
1058:
1059: // So if we wind up here, there is no way to update the
1060: // underlying DataStore
1061: WbSwingUtilities.showErrorMessageKey(w, "MsgNoTables");
1062: this .setUpdateTable(null);
1063: this .disableUpdateActions();
1064: this .selectKeys.setEnabled(false);
1065: return false;
1066: }
1067:
1068: if (updateable) {
1069: this .dataTable.setShowStatusColumn(true);
1070: this .editingStarted = true;
1071:
1072: // When changing the table model (which is happening
1073: // when the status column is displayed) we need to restore
1074: // the current editing column/row
1075: if (restoreSelection) {
1076: if (currentRow > -1 && currentColumn > -1) {
1077: dataTable.selectCell(currentRow, currentColumn + 1);
1078: dataTable.setColumnSelectionAllowed(false);
1079: } else if (currentRow > -1) {
1080: dataTable.scrollToRow(currentRow);
1081: dataTable.setRowSelectionInterval(currentRow,
1082: currentRow);
1083: } else if (selectedRows != null) {
1084: ListSelectionModel model = dataTable
1085: .getSelectionModel();
1086: model.setValueIsAdjusting(true);
1087: // make sure nothing is selected, then restore the old selection
1088: model.clearSelection();
1089: for (int i = 0; i < selectedRows.length; i++) {
1090: model.addSelectionInterval(selectedRows[i],
1091: selectedRows[i]);
1092: }
1093: model.setValueIsAdjusting(false);
1094: }
1095: dataTable.requestFocusInWindow();
1096: }
1097:
1098: EventQueue.invokeLater(new Runnable() {
1099: public void run() {
1100: checkResultSetActions();
1101: }
1102: });
1103: } else {
1104: String msg = null;
1105: TableIdentifier tbl = (this .dataTable.getDataStore() != null ? this .dataTable
1106: .getDataStore().getUpdateTable()
1107: : null);
1108: if (tbl == null) {
1109: msg = ResourceMgr.getString("MsgNoUpdateTable");
1110: } else if (!this .hasUpdateableColumns()) {
1111: msg = ResourceMgr.getString("MsgNoUpdateColumns");
1112: msg = StringUtil.replace(msg, "%table%", tbl
1113: .getTableExpression());
1114: }
1115: WbSwingUtilities.showErrorMessage(w, msg);
1116: }
1117:
1118: return updateable;
1119: }
1120:
1121: public InsertRowAction getInsertRowAction() {
1122: return this .insertRow;
1123: }
1124:
1125: public CopyRowAction getCopyRowAction() {
1126: return this .duplicateRow;
1127: }
1128:
1129: public DeleteRowAction getDeleteRowAction() {
1130: return this .deleteRow;
1131: }
1132:
1133: public DeleteDependentRowsAction getDeleteDependentRowsAction() {
1134: return this .deleteDependentRow;
1135: }
1136:
1137: public UpdateDatabaseAction getUpdateDatabaseAction() {
1138: return this .updateAction;
1139: }
1140:
1141: /**
1142: * Turns on the batchUpdate mode.
1143: * In this mode, the automatic switch to edit mode is disabled. This
1144: * is used when populating the table from the database otherwise, the
1145: * first row, which is retrieved will start the edit mode
1146: */
1147: public void setBatchUpdate(boolean aFlag) {
1148: this .batchUpdate = aFlag;
1149: }
1150:
1151: public RowActionMonitor getRowMonitor() {
1152: return this .genericRowMonitor;
1153: }
1154:
1155: /**
1156: * If the user changes something in the result set (which is possible, as
1157: * the table defaults to beeing editable) the edit mode (with status column
1158: * and the different actions enabled) is switched on automatically.
1159: */
1160: public void tableChanged(TableModelEvent e) {
1161: if (this .batchUpdate)
1162: return;
1163: if (this .readOnly)
1164: return;
1165:
1166: if (!editingStarted
1167: && e.getFirstRow() != TableModelEvent.ALL_COLUMNS
1168: && e.getFirstRow() != TableModelEvent.HEADER_ROW
1169: && this .isModified()) {
1170: EventQueue.invokeLater(new Runnable() {
1171: public void run() {
1172: startEdit();
1173: }
1174: });
1175: }
1176: }
1177:
1178: /**
1179: * This is called when the selection in the table changes.
1180: * The copy row action will be enabled when exactly one row
1181: * is selected
1182: */
1183: public void valueChanged(ListSelectionEvent e) {
1184: if (this .readOnly)
1185: return;
1186: checkResultSetActions();
1187: }
1188:
1189: /**
1190: * Called from the viewport, when the display has been scrolled
1191: * We need to update the row display then.
1192: */
1193: public void stateChanged(ChangeEvent e) {
1194: this .rowCountChanged();
1195: }
1196:
1197: public void fatalError(String msg) {
1198: WbSwingUtilities.showErrorMessage(this, msg);
1199: }
1200:
1201: }
|