/* ---- This file is part of SECONDO. Copyright (C) 2004, University in Hagen, Department of Computer Science, Database Systems for New Applications. SECONDO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. SECONDO 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 General Public License for more details. You should have received a copy of the GNU General Public License along with SECONDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- 04-2005, Matthias Zielke 01-2006, T. Behr: generic approach for displaying different types realized 12-2006, T. Behr: SplitPane between relation and insertions introduced Removing rows from insertions implemented */ package viewer; import java.awt.*; import java.awt.event.*; import java.util.Vector; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.*; import javax.swing.table.*; import javax.swing.event.*; import gui.SecondoObject; import viewer.update.*; import sj.lang.*; import tools.Reporter; /* This specialized viewer shall only be used for updating relations. Therefore it can not be called for any object retrieved by commands from the CommandPanel. It asks itself for relations to be loaded and displays them. Afterwards update-operations can be applied to the relation and the changes are finally sent to SECONDO. If succesfull the new updated relation will be shown, otherwise an errormessage should be displayed. */ public class UpdateViewer extends SecondoViewer implements TableModelListener { private String viewerName = "UpdateViewer"; // Needed to build the actionPanel that offers the user possible update-operations private JPanel actionPanel; private JTextField relField; private JButton load; private JButton clear; private JButton insert; private JButton delete; private JButton update; private JButton reset; private JButton commit; private JButton popup; private EditDialog editDialog; // the controller decides which action shall be taken next and listens to all buttons // for user-input private ActionController controller; // ScrollPanes to show the update-relation or a relation with new tuples to be inserted private JScrollPane relScroll; private JScrollPane insertScroll; private JSplitPane insertSplit= new JSplitPane(JSplitPane.VERTICAL_SPLIT); // Dialog to show errormessages private JDialog errorDialog; // Dialog to specify relation to be loaded and filters to be applied private LoadDialog loadDialog; // stores the data actually shown in the viewer private String[][] relData; // stores the data retrieved from secondo for the originally loaded relation private String[][] originalData; // Contains the IDs of all tuples of the actual relation private Vector tupleIds; // Shows the relation currently edited private JTable relTable; private String[] head; private String[] attrTypes; // In updatemode contains true for each updated attribute for each record private boolean[][] changedCells; // For each updated row an entry with its index is inserted private Vector changedRows; // Contains all changed Cells in the order they were edited private Vector updatesOrdered; // Contains the information of the inserted tuples private String[][] insertData; // Shows the relation to insert new tuples private JTable insertTable; // Each updated attribute is marked by this renderer private DefaultTableCellRenderer renderer; private boolean relEditable; /* Builds the viewer and sets the intital values */ public UpdateViewer() { setLayout(new BorderLayout()); controller = new ActionController(this); // Build actionpanel actionPanel = new JPanel(); actionPanel.setLayout(new GridLayout(1, 7)); load = new JButton("Load Relation"); load.addActionListener(controller); actionPanel.add(load); clear = new JButton("Clear"); clear.addActionListener(controller); actionPanel.add(clear); insert = new JButton("Insert"); insert.addActionListener(controller); actionPanel.add(insert); delete = new JButton("Delete"); delete.addActionListener(controller); actionPanel.add(delete); update = new JButton("Update"); update.addActionListener(controller); actionPanel.add(update); reset = new JButton("Reset"); reset.addActionListener(controller); actionPanel.add(reset); commit = new JButton("Commit"); commit.addActionListener(controller); actionPanel.add(commit); editDialog=new EditDialog(); popup = new JButton("Popup"); popup.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent evt){ UpdateViewer.this.editField(); } }); actionPanel.add(popup); add(actionPanel, BorderLayout.NORTH); renderer = new DefaultTableCellRenderer(); renderer.setBackground(Color.YELLOW); relEditable = false; setSelectionMode(ActionController.INITIAL); } /* Get the name of this viewer. The name is used in the menu of the MainWindow. */ public String getName() { return viewerName; } /* Because this viewer shall not display objects others than relations loaded by the viewer itself only false is returned. */ public boolean addObject(SecondoObject o) { return false; } /* Because this viewer shall not display objects others than relations loaded by the viewer itself no object shall be removed. */ public void removeObject(SecondoObject o) { } /* Because this viewer shall not display objects others than relations loaded by the viewer itself no objects shall be removed. */ public void removeAll() { } /* Because this viewer shall not display objects others than relations loaded by the viewer itself false is returned. */ public boolean canDisplay(SecondoObject o) { return false; } /* Because this viewer shall not display objects others than relations loaded by the viewer itself false is returned. */ public boolean isDisplayed(SecondoObject o) { return false; } /* Because this viewer shall not display objects others than relations loaded by the viewer itself false is returned. */ public boolean selectObject(SecondoObject O) { return false; } /* Because all commands for this viewer can easily be accessed from the actionPanel no MenuVector is built. */ public MenuVector getMenuVector() { return null; } /* Because this viewer shall not display objects others than relations loaded by the viewer itself 0 is returned. */ public double getDisplayQuality(SecondoObject SO) { return 0; } /* Shows a dialog with the errorText. */ public void showErrorDialog(String errorText) { Reporter.showError(errorText); this.repaint(); this.validate(); } /* If the paramater 'relation' is really a relation it will be shown in the viewer. Otherwise false will be returned. */ public boolean showNewRelation(ListExpr relation) { relTable = createTableFrom(relation); if (relTable == null) { return false; } relScroll = new JScrollPane(); add(relScroll, BorderLayout.CENTER); relTable.setRowSelectionAllowed(false); relTable.setColumnSelectionAllowed(false); relEditable = false; TableModel model = relTable.getModel(); model.addTableModelListener(this); relScroll.setViewportView(relTable); this.repaint(); this.validate(); return true; } /* Shows the original relation retrieved from SECONDO. All updates that have not been commited yet will be lost. */ public void showOriginalRelation() { relTable.setRowSelectionAllowed(false); relTable.setColumnSelectionAllowed(false); relEditable = false; for (int i = 0; i < relData.length; i++) { for (int j = 0; j < relData[0].length; j++) { changedCells[i][j] = false; changedRows.clear(); relData[i][j] = new String(originalData[i][j]); } } relTable.clearSelection(); relTable.invalidate(); this.repaint(); this.validate(); } /* Removes the relation that could be edited to insert new tuples. */ public void removeInsertRelation() { // this.remove(insertScroll); this.remove(insertSplit); this.add(relScroll, BorderLayout.CENTER); insertData = null; insertTable = null; insertScroll = null; this.validate(); } /* Adds remove functionality to the insertTable */ private void addRemoveToInsertTable(){ if(insertTable==null){ return; } insertTable.addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent evt){ if( (evt.getKeyCode()==KeyEvent.VK_DELETE)){ removeInsertSelection(); } } }); } /* Removes the last added tuple from the insert-relation */ public boolean removeLastInsertTuple(){ if (insertData.length == 1){ removeInsertRelation(); return false; } else { String[][] newInsertData = new String[insertData.length - 1][insertData[0].length]; for (int i = 0; i < insertData.length -1 ; i++) { newInsertData[i] = insertData[i]; } insertData = newInsertData; insertTable = new JTable(insertData, head); insertScroll.setViewportView(insertTable); insertSplit.setBottomComponent(insertScroll); this.add(insertSplit,BorderLayout.CENTER); insertSplit.revalidate(); insertSplit.setDividerLocation(0.5); addRemoveToInsertTable(); // this.add(insertScroll, BorderLayout.SOUTH); this.validate(); this.repaint(); return true; } } /* Removes the relation actually shown from this viewer. All information about this relation will be lost. */ public void removeRelation() { this.remove(relScroll); relData = null; relTable = null; head = null; attrTypes = null; changedCells = null; changedRows = null; originalData = null; this.validate(); this.repaint(); } /** Pops up a new window for editing the current field. */ private void editField(){ if(relTable==null){ Reporter.showError("No table load."); return; } int row = relTable.getSelectedRow(); int col = relTable.getSelectedColumn(); if(row<0 || col<0){ row = relTable.getEditingRow(); col = relTable.getEditingRow(); } int er = relTable.getEditingRow(); int ec = relTable.getEditingColumn(); if(er>=0 && ec >=0){ relTable.getCellEditor(er,ec).stopCellEditing(); } relTable.removeEditor(); if(row<0 || col<0){ Reporter.showError("Select a field first"); return; } String res = editDialog.editText(relTable.getValueAt(row,col).toString()); if(res!=null){ relTable.setValueAt(res,row,col); } } /* For each mode and state the viewer is in only certain operations and choices are possible. This method assures only the actually allowed actions can be executed or chosen. */ public void setSelectionMode(int selectMode) { switch (selectMode) { case ActionController.INITIAL: { insert.setBackground(Color.LIGHT_GRAY); delete.setBackground(Color.LIGHT_GRAY); update.setBackground(Color.LIGHT_GRAY); clear.setEnabled(false); insert.setEnabled(false); delete.setEnabled(false); update.setEnabled(false); reset.setEnabled(false); commit.setEnabled(false); popup.setEnabled(false); break; } case ActionController.LOADED: { insert.setBackground(Color.LIGHT_GRAY); delete.setBackground(Color.LIGHT_GRAY); update.setBackground(Color.LIGHT_GRAY); clear.setEnabled(true); insert.setEnabled(true); delete.setEnabled(true); update.setEnabled(true); reset.setEnabled(false); commit.setEnabled(false); popup.setEnabled(false); break; } case ActionController.INSERT: { insert.setBackground(Color.YELLOW); delete.setEnabled(false); update.setEnabled(false); reset.setEnabled(true); commit.setEnabled(true); relTable.setRowSelectionAllowed(false); relTable.setColumnSelectionAllowed(false); relEditable = false; popup.setEnabled(false); break; } case ActionController.DELETE: { delete.setBackground(Color.YELLOW); insert.setEnabled(false); delete.setEnabled(false); update.setEnabled(false); reset.setEnabled(true); commit.setEnabled(true); relTable.setRowSelectionAllowed(true); relEditable = false; popup.setEnabled(false); break; } case ActionController.UPDATE: { update.setBackground(Color.YELLOW); insert.setEnabled(false); delete.setEnabled(false); update.setEnabled(false); reset.setEnabled(true); commit.setEnabled(true); relTable.setRowSelectionAllowed(false); relTable.setColumnSelectionAllowed(false); relEditable = true; popup.setEnabled(true); for (int i = 0; i < relData.length; i++) { for (int j = 0; j < relData[0].length; j++) { changedCells[i][j] = false; } } break; } default: showErrorDialog("The mode: " + selectMode + " is not known"); break; } } /** Removes all selected rows from the InsertTable. **/ private void removeInsertSelection(){ if(insertTable==null){ // no table available return; } if(insertTable.isEditing()){ return; } int[] selectedRows = insertTable.getSelectedRows(); if( selectedRows.length==0){ JOptionPane.showMessageDialog(this,"Nothing selected"); return; } int answer = JOptionPane.showConfirmDialog(this,"All selected rows will be deleted\n Do you want to continue?", "Please Confirm", JOptionPane.YES_NO_OPTION); if((answer!=JOptionPane.YES_OPTION)){ return; } String[][] newInsertData = new String[insertData.length-selectedRows.length][insertData[0].length]; // copy all non-removed rows int pos = 0; for(int i=0;i < insertData.length;i++){ if(pos>=selectedRows.length){ // all selected rows are removed already newInsertData[i-pos] = insertData[i]; } else if(i==selectedRows[pos]){ // remove this row pos++; } else { newInsertData[i-pos] = insertData[i]; } } insertData = newInsertData; insertTable = new JTable(insertData, head); addRemoveToInsertTable(); insertScroll.setViewportView(insertTable); int lastPos = insertSplit.getDividerLocation(); insertSplit.setBottomComponent(insertScroll); this.add(insertSplit,BorderLayout.CENTER); this.validate(); this.repaint(); insertSplit.setDividerLocation(lastPos); } /* Creates a JTable from the parameter LE. If LE doesn't represent a relation 'null' will be returned. The members that represent the original relation-data and the actual relation-data are initialized. The IDs of the tuples of the relation are stored in a seperate vector */ private JTable createTableFrom(ListExpr LE) { boolean result = true; JTable NTable = null; if (LE.listLength() != 2) return null; else { ListExpr type = LE.first(); ListExpr value = LE.second(); // analyse type if (type.isAtom()) return null; ListExpr maintype = type.first(); if (type.listLength() != 2 || !maintype.isAtom() || maintype.atomType() != ListExpr.SYMBOL_ATOM || !(maintype.symbolValue().equals("rel") | maintype .symbolValue().equals("mrel"))) return null; // not a relation ListExpr tupletype = type.second(); // analyse Tuple ListExpr TupleFirst = tupletype.first(); if (tupletype.listLength() != 2 || !TupleFirst.isAtom() || TupleFirst.atomType() != ListExpr.SYMBOL_ATOM || !(TupleFirst.symbolValue().equals("tuple") | TupleFirst .symbolValue().equals("mtuple"))) return null; // not a tuple ListExpr TupleTypeValue = tupletype.second(); // the table head // Don't count the last attribute which is the tupleidentifier of each tuple int tupleLength = TupleTypeValue.listLength() - 1; head = new String[tupleLength]; attrTypes = new String[tupleLength]; for (int i = 0; (i < tupleLength) && result; i++) { ListExpr TupleSubType = TupleTypeValue.first(); if (TupleSubType.listLength() != 2) result = false; else { head[i] = TupleSubType.first().writeListExprToString(); attrTypes[i] = TupleSubType.second() .writeListExprToString(); } TupleTypeValue = TupleTypeValue.rest(); } if (result) { // analyse the values ListExpr TupleValue; Vector V = new Vector(); tupleIds = new Vector(); String[] row; ListExpr Elem; while (!value.isEmpty()) { TupleValue = value.first(); row = new String[head.length]; for(int pos=0;pos 0){ relTable.clearSelection(); return true; } else{ return false; } } /* Returns the indices of all tuples that were marked to be deleted. */ public int[] getDeleteRows(){ return relTable.getSelectedRows(); } /* Returns the original values of the tuple at position 'index'. */ public String[] getOriginalTuple(int index) { return originalData[index]; } /* Returns the tupleId of the record at position 'index'. */ public String getTupleId(int index){ return (String)(tupleIds.get(index)); } /* Returns an array with the indices of all updated tuples. */ public int[] updatedTuples() { int[] updateTuples = new int[changedRows.size()]; for (int i = 0; i < updateTuples.length; i++) { updateTuples[i] = ((Integer) changedRows.get(i)).intValue(); } return updateTuples; } /* Returns the values of the updated tuple at position 'index'. */ public String[] getUpdateTuple(int index) { return relData[index]; } /* For the tuple at position 'index' returns all indices of the attributes that have been changed inside this tuple. */ public int[] getChangedAttributes(int index) { boolean[] attrs = changedCells[index]; Vector changedAttrs = new Vector(); for (int i = 0; i < changedCells[index].length; i++) { if (changedCells[index][i]) { changedAttrs.add(new Integer(i)); } } int[] result = new int[changedAttrs.size()]; for (int i = 0; i < result.length; i++) { result[i] = ((Integer) changedAttrs.get(i)).intValue(); } return result; } /* Resets the last editited cell in updatemode to its original value. */ public boolean resetLastUpdate(){ if (changedRows.size() > 0) { int[] lastChanged = (int[]) updatesOrdered.lastElement(); changedCells[lastChanged[0]][lastChanged[1]] = false; changedRows.remove(changedRows.size() - 1); updatesOrdered.remove(updatesOrdered.size() - 1); relData[lastChanged[0]][lastChanged[1]] = originalData[lastChanged[0]][lastChanged[1]]; this.repaint(); this.validate(); return true; } else{ return false; } } /* Resets all updates */ public boolean resetUpdates(){ if(relTable==null) return false; // reset the curretly editing cell if any if(relTable.isEditing()){ int x = relTable.getEditingRow(); int y = relTable.getEditingColumn(); relTable.editingStopped(new ChangeEvent(this)); relTable.setValueAt(originalData[x][y],x,y); } for(int i=0;i