001: package net.sourceforge.squirrel_sql.fw.datasetviewer;
002:
003: /*
004: * Copyright (C) 2001-2003 Colin Bell
005: * colbell@users.sourceforge.net
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021:
022: import net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.CellComponentFactory;
023: import net.sourceforge.squirrel_sql.fw.gui.SortableTableModel;
024: import net.sourceforge.squirrel_sql.fw.gui.TablePopupMenu;
025: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
026: import net.sourceforge.squirrel_sql.fw.util.StringManager;
027:
028: import javax.swing.*;
029: import javax.swing.table.TableColumnModel;
030: import javax.swing.table.TableColumn;
031: import java.awt.*;
032: import java.awt.event.MouseAdapter;
033: import java.awt.event.MouseEvent;
034: import java.util.ArrayList;
035:
036: /**
037: * @author gwg
038: *
039: * Table view that allows editing of the data.
040: */
041: public class DataSetViewerEditableTablePanel extends
042: DataSetViewerTablePanel {
043:
044: private static final StringManager s_stringMgr = StringManagerFactory
045: .getStringManager(DataSetViewerEditableTablePanel.class);
046:
047: /* Menu for right-mouse-click when in cell editors */
048: TablePopupMenu cellPopupMenu = null;
049:
050: /**
051: * Internal definitions
052: */
053: public void init(IDataSetUpdateableModel updateableModel) {
054: super .init(updateableModel);
055: setUpdateableModelReference(updateableModel);
056: }
057:
058: ///////////////////////////////////////////////////////////////////////////
059: //
060: // Override the functions that need to be changed to tell the table
061: // mechanisms how to do editing.
062: //
063: //////////////////////////////////////////////////////////////////////////
064:
065: /**
066: * Tell the table that it is editable. This is called from within
067: * MyTable.isCellEditable(). Certain column data types may not be editable.
068: */
069: public boolean isTableEditable() {
070: return true;
071: }
072:
073: /**
074: * Tell the table whether a particular column may be edited or not
075: * based on whether the class associated with that column is known
076: * or not known, where "not known" is signaled by Object.class.
077: */
078: public boolean isColumnEditable(int col, Object originalValue) {
079: if (_colDefs == null)
080: return false; // cannot edit something that we do not know anything about
081:
082: if (RowNumberTableColumn.ROW_NUMBER_MODEL_INDEX == col)
083: return false;
084:
085: // Cannot edit the rowID column, if present
086: if (((IDataSetUpdateableTableModel) getUpdateableModel())
087: .getRowidCol() == col)
088: return false;
089:
090: return CellComponentFactory.isEditableInCell(_colDefs[col],
091: originalValue);
092: }
093:
094: /**
095: * Function to set up CellEditors for each of the data types
096: * to be handled in this table. Since different columns have different
097: * parameters (e.g. nullable or not nullable) we set the cell editors on the columns
098: * rather than on the table as a whole.
099: */
100: public void setCellEditors(JTable table) {
101: // we need to table column model to be able to add CellEditors to the
102: // individual columns
103: cellPopupMenu = new TablePopupMenu(getUpdateableModel(), this ,
104: table);
105:
106: for (int i = 0; i < _colDefs.length; i++) {
107: // use factory to get the appropriate editor
108: DefaultCellEditor editor = CellComponentFactory
109: .getInCellEditor(table, _colDefs[i]);
110:
111: // add right-click menu to cell editor
112: editor.getComponent().addMouseListener(new MouseAdapter() {
113: public void mousePressed(MouseEvent evt) {
114: if (evt.isPopupTrigger()) {
115: DataSetViewerEditableTablePanel.this .cellPopupMenu
116: .show(evt.getComponent(), evt.getX(),
117: evt.getY());
118: }
119: }
120:
121: public void mouseReleased(MouseEvent evt) {
122: if (evt.isPopupTrigger()) {
123: DataSetViewerEditableTablePanel.this .cellPopupMenu
124: .show(evt.getComponent(), evt.getX(),
125: evt.getY());
126: }
127: }
128: });
129:
130: // We have to look for the modelindex because of the Row Number column
131: getColumnForModelIndex(i, table.getColumnModel())
132: .setCellEditor(editor);
133: }
134: }
135:
136: private TableColumn getColumnForModelIndex(int modelIndex,
137: TableColumnModel columnModel) {
138: for (int i = 0; i < columnModel.getColumnCount(); i++) {
139: if (columnModel.getColumn(i).getModelIndex() == modelIndex) {
140: return columnModel.getColumn(i);
141: }
142: }
143:
144: throw new IllegalArgumentException("No column for model index "
145: + modelIndex);
146: }
147:
148: /**
149: * Call the underlaying object to update the data represented by the JTable.
150: * Both the old and the new value are objects of the appropriate
151: * Data Type for the column. The newValue has been validated as part of
152: * the conversion from the external user representation (a String) into the
153: * internal object.
154: */
155: public int[] changeUnderlyingValueAt(int row, int col,
156: Object newValue, Object oldValue) {
157: String message = null;
158:
159: // At this point the user input has been validated and both the
160: // new and old values are objects of the appropriate data type.
161: // Either or both of newValue and oldValue may be null.
162:
163: // if there is no updateable model, then we cannot update anything
164: // (should never happen - just being safe here)
165: if (getUpdateableModelReference() == null)
166: return new int[0]; // no underlying data, so cannot be changed
167:
168: // check to see if new data is same as old data, in which case we
169: // do not update the underlying data.
170: //
171: // This is NOT an optimization (though it does
172: // speed things up). We need to do this to avoid an error when we check for
173: // rows being changed in the DB. If the new value and old value are the same,
174: // when we look to see if any rows already exist with the new value, it will find
175: // the existing row and claim that the update will make one row identical to the
176: // changed row (i.e. that there will be two identical rows in the DB) which is
177: // not true. So we avoid the problem by not updating the DB if the data has not
178: // been changed. This can happen if user changes the cell contents, then changes
179: // them back before exiting the cell.
180:
181: // first look to see if they are identical objects, e.g. both null
182: if (newValue == oldValue)
183: return new int[0]; // the caller does not need to know that nothing happened
184:
185: // if either of the values is null and the other is not, then the data has
186: // changed and we fall-through to the change process. Otherwise, check
187: // the object contents.
188: if (oldValue != null && newValue != null) {
189: // ask the DataType object if the two values are the same
190: if (CellComponentFactory.areEqual(_colDefs[col], oldValue,
191: newValue))
192: return new int[0]; // the caller does not need to know that nothing happened
193:
194: // if we reach this point, the value has been changed,
195: // so fall through to next section
196: }
197:
198: // call the function in the app code that checks for unexpected
199: // conditions in the current DB
200: if (getUpdateableModelReference() != null)
201: message = ((IDataSetUpdateableTableModel) getUpdateableModelReference())
202: .getWarningOnCurrentData(getRow(row), _colDefs,
203: col, oldValue);
204:
205: if (message != null) {
206: // set up dialog to ask user if it is ok to proceed
207: // IMPORTANT: this dialog is SYNCHRONOUS (ie. we do not proceed until
208: // user gives a response). This is critical since this function provides
209: // a return value to its caller that depends on the user input.
210: // i18n[baseDataSetViewerDestination.warning=Warning]
211: int option = JOptionPane
212: .showConfirmDialog(
213: null,
214: message,
215: s_stringMgr
216: .getString("baseDataSetViewerDestination.warning"),
217: JOptionPane.YES_NO_OPTION,
218: JOptionPane.WARNING_MESSAGE);
219: if (option != JOptionPane.YES_OPTION) {
220: return new int[0]; // no update done to underlying data
221: }
222: }
223:
224: // call the function in the app code that checks for unexpected
225: // conditions in the DB as it will be after doing the update
226: if (getUpdateableModelReference() != null)
227: message = ((IDataSetUpdateableTableModel) getUpdateableModelReference())
228: .getWarningOnProjectedUpdate(getRow(row), _colDefs,
229: col, newValue);
230:
231: if (message != null) {
232: // set up dialog to ask user if it is ok to proceed
233: // IMPORTANT: this dialog is SYNCHRONOUS (ie. we do not proceed until
234: // user gives a response). This is critical since this function provides
235: // a return value to its caller that depends on the user input.
236: // i18n[baseDataSetViewerDestination.warning2=Warning]
237: int option = JOptionPane
238: .showConfirmDialog(
239: null,
240: message,
241: s_stringMgr
242: .getString("baseDataSetViewerDestination.warning2"),
243: JOptionPane.YES_NO_OPTION,
244: JOptionPane.WARNING_MESSAGE);
245: if (option != JOptionPane.YES_OPTION) {
246: return new int[0]; // no update done to underlying data
247: }
248: }
249:
250: // call the function in the app code that saves the data in the
251: // persistant storage (e.g. a database).
252: // The success or failure of that function (as indicated by the absance or
253: // presence of a result errpor/warning message) determines the result of this call.
254: // (Since the table is supposed to be editable, we should have an
255: // IDataSetUpdateableTableModel object set in our super class.)
256:
257: message = ((IDataSetUpdateableTableModel) getUpdateableModelReference())
258: .updateTableComponent(getRow(row), _colDefs, col,
259: oldValue, newValue);
260:
261: if (message != null) {
262: // tell user that there was a problem
263: // i18n[baseDataSetViewerDestination.error=Error]
264: JOptionPane.showMessageDialog(null, message, s_stringMgr
265: .getString("baseDataSetViewerDestination.error"),
266: JOptionPane.ERROR_MESSAGE);
267:
268: // tell caller that the underlying data was not updated
269: //?? is this always true, or could the data be updated with a warning?
270: return new int[0];
271: }
272:
273: // No problems, so indicate a successful update of the underlying data.
274: // In case we are editing an SQL result that contains the edited colum
275: // more than once, we need to tell the caller to update all columns.
276: // Otherwise generation of where clauses for further editing will fail.
277: ArrayList<Integer> buf = new ArrayList<Integer>();
278: for (int i = 0; i < _colDefs.length; i++) {
279: if (_colDefs[i].getFullTableColumnName().equalsIgnoreCase(
280: _colDefs[col].getFullTableColumnName())) {
281: buf.add(Integer.valueOf(i));
282: }
283: }
284:
285: int[] ret = new int[buf.size()];
286:
287: for (int i = 0; i < ret.length; i++) {
288: ret[i] = buf.get(i);
289: }
290:
291: return ret;
292: }
293:
294: /**
295: * Delete a set of rows from the table.
296: * The indexes are the row indexes in the SortableModel.
297: */
298: public void deleteRows(int[] rows) {
299: // The list of rows may be empty, in which case
300: // we tell user they should select something first
301: if (rows.length == 0) {
302: JOptionPane
303: .showMessageDialog(
304: null,
305: // i18n[dataSetViewerEditableTablePanel.selectionNeeded=You must select something in the table to delete.]
306: s_stringMgr
307: .getString("dataSetViewerEditableTablePanel.selectionNeeded"));
308: return;
309: }
310:
311: // i18n[dataSetViewerEditableTablePanel.deleteRosQuestion=Do you wish to delete {0} rows from this table?]
312: String msg = s_stringMgr.getString(
313: "dataSetViewerEditableTablePanel.deleteRosQuestion",
314: rows.length);
315:
316: // Non-empty set of rows to delete. Make sure user wants to delete
317: int option = JOptionPane
318: .showConfirmDialog(
319: null,
320: msg,
321: // i18n[dataSetViewerEditableTablePanel.warning=Warning]
322: s_stringMgr
323: .getString("dataSetViewerEditableTablePanel.warning"),
324: JOptionPane.YES_NO_OPTION,
325: JOptionPane.WARNING_MESSAGE);
326:
327: if (option != JOptionPane.YES_OPTION) {
328: return; // no update done to underlying data
329: }
330:
331: //cancel any current cell editing operations
332: if (currentCellEditor != null) {
333: currentCellEditor.cancelCellEditing();
334: currentCellEditor = null;
335: }
336:
337: // create data structure containing contents of rows to be deleted
338: // We cannot use the getRow() method because that uses MyJTable whereas
339: // the indexes that we have are indexes in the SortableTableModel.
340: SortableTableModel tableModel = (SortableTableModel) ((JTable) getComponent())
341: .getModel();
342:
343: Object[][] rowData = new Object[rows.length][_colDefs.length];
344: for (int i = 0; i < rows.length; i++) {
345: for (int j = 0; j < _colDefs.length; j++)
346: rowData[i][j] = tableModel.getValueAt(rows[i], j);
347: }
348:
349: // tell creator to delete from DB
350: String message = ((IDataSetUpdateableTableModel) getUpdateableModel())
351: .deleteRows(rowData, _colDefs);
352:
353: if (message != null) {
354: // tell user that there was a problem
355: JOptionPane
356: .showMessageDialog(
357: null,
358: // i18n[dataSetViewerEditableTablePanel.noRowsDeleted={0}\nNo rows deleted from database.]
359: s_stringMgr
360: .getString(
361: "dataSetViewerEditableTablePanel.noRowsDeleted",
362: message),
363: // i18n[dataSetViewerEditableTablePanel.error=Error]
364: s_stringMgr
365: .getString("dataSetViewerEditableTablePanel.error"),
366: JOptionPane.ERROR_MESSAGE);
367:
368: return;
369: }
370:
371: // DB delete worked correctly, so now delete from table
372: //IMPORTANT: The user and the creator both work through the
373: // SortableTableModel, not the Actual model. Thus the row
374: // indexes to delete are given in the SortableTableModel row numbers,
375: // so we must work through that model model to actually do the delete.
376: ((SortableTableModel) ((MyJTable) getComponent()).getModel())
377: .deleteRows(rows);
378: ((MyJTable) getComponent()).clearSelection();
379: }
380:
381: /**
382: * Initiate operations to insert a new row into the table.
383: * This method just creates the panel to get the row input from the user.
384: */
385: public void insertRow() {
386: JTable table = (JTable) getComponent();
387:
388: // Setting the starting position is ugly. I just picked a point.
389: Point pt = new Point(10, 200);
390:
391: Component comp = SwingUtilities.getRoot(table);
392: Component newComp = null;
393:
394: // get the default values from the DB for the table columns
395: String[] dbDefaultValues = ((IDataSetUpdateableTableModel) getUpdateableModelReference())
396: .getDefaultValues(_colDefs);
397:
398: // based on defaults from DB, get the default object instance
399: // for each column
400: Object[] initialValues = new Object[dbDefaultValues.length];
401: for (int i = 0; i < initialValues.length; i++) {
402: initialValues[i] = CellComponentFactory.getDefaultValue(
403: _colDefs[i], dbDefaultValues[i]);
404: }
405:
406: // The following only works if SwingUtilities.getRoot(table) returns
407: // and instanceof BaseMDIParentFrame.
408: // If SwingTUilities.getRoot(table) returns and instance of Dialog or
409: // Frame, then other code must be used.
410: RowDataInputFrame rdif = new RowDataInputFrame(_colDefs,
411: initialValues, this );
412: ((IMainFrame) comp).addInternalFrame(rdif, false);
413: rdif.setLayer(JLayeredPane.POPUP_LAYER);
414: rdif.pack();
415: newComp = rdif;
416:
417: Dimension dim = newComp.getSize();
418: boolean dimChanged = false;
419: if (dim.width < 300) {
420: dim.width = 300;
421: dimChanged = true;
422: }
423:
424: if (dimChanged) {
425: newComp.setSize(dim);
426: }
427:
428: // Determine the position to place the new internal frame. Ensure that the right end
429: // of the internal frame doesn't exend past the right end the parent frame. Use a
430: // fudge factor as the dim.width doesn't appear to get the final width of the internal
431: // frame (e.g. where pt.x + dim.width == parentBounds.width, the new internal frame
432: // still extends past the right end of the parent frame).
433: int fudgeFactor = 100;
434: Rectangle parentBounds = comp.getBounds();
435: if (parentBounds.width <= (dim.width + fudgeFactor)) {
436: dim.width = parentBounds.width - fudgeFactor;
437: pt.x = fudgeFactor / 2;
438: newComp.setSize(dim);
439: } else {
440: if ((pt.x + dim.width + fudgeFactor) > (parentBounds.width)) {
441: pt.x -= (pt.x + dim.width + fudgeFactor)
442: - parentBounds.width;
443: }
444: }
445:
446: newComp.setLocation(pt);
447: newComp.setVisible(true);
448: }
449:
450: /**
451: * Insert a new row into the table after the user has entered the row's data.
452: */
453: protected String insertRow(Object[] values) {
454:
455: String message = ((IDataSetUpdateableTableModel) getUpdateableModelReference())
456: .insertRow(values, _colDefs);
457:
458: if (message != null) {
459: // there was a problem inserting into the DB
460: JOptionPane
461: .showMessageDialog(
462: null,
463: // i18n[dataSetViewereditableTablePanel.error2=Error]
464: message,
465: s_stringMgr
466: .getString("dataSetViewereditableTablePanel.error2"),
467: JOptionPane.ERROR_MESSAGE);
468:
469: return "Error"; // non-null return tells caller there was a problem
470: }
471:
472: // add the data to the existing tables
473:
474: // Do not try to be fancy and insert the data where the user is looking,
475: // just stuff it into the actual model and re-paint the table
476: // when the 'table changed' event is fired.
477:
478: SortableTableModel sortedModel = (SortableTableModel) ((JTable) getComponent())
479: .getModel();
480:
481: sortedModel.insertRow(values);
482:
483: // everything is ok
484: return null;
485: }
486: }
|