531 lines
16 KiB
Java
531 lines
16 KiB
Java
/* Copyright (c) 2008 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
package sample.spreadsheet.gui;
|
|
|
|
import com.google.gdata.client.spreadsheet.ListQuery;
|
|
import com.google.gdata.client.spreadsheet.SpreadsheetService;
|
|
import com.google.gdata.data.TextContent;
|
|
import com.google.gdata.data.spreadsheet.CustomElementCollection;
|
|
import com.google.gdata.data.spreadsheet.ListEntry;
|
|
import com.google.gdata.data.spreadsheet.ListFeed;
|
|
import com.google.gdata.util.ServiceException;
|
|
import com.google.gdata.util.VersionConflictException;
|
|
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Container;
|
|
import java.awt.GridBagConstraints;
|
|
import java.awt.GridBagLayout;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.io.IOException;
|
|
import java.net.URL;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.TreeSet;
|
|
|
|
import javax.swing.JButton;
|
|
import javax.swing.JFrame;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTable;
|
|
import javax.swing.JTextField;
|
|
import javax.swing.table.AbstractTableModel;
|
|
|
|
/**
|
|
* Widget for displaying and editing a spreadsheet.
|
|
*
|
|
* This is just for demonstrative purposes, it is not very featureful.
|
|
*
|
|
*
|
|
*/
|
|
public class ListBasedSpreadsheetPanel extends JPanel {
|
|
|
|
/** The Google Spreadsheets service. */
|
|
private SpreadsheetService service;
|
|
|
|
/** The list feed URL. */
|
|
private URL listFeedUrl;
|
|
|
|
/** The model that Swing uses to represent the table. */
|
|
private ListTableModel model;
|
|
|
|
/** The table widget. */
|
|
private JTable table;
|
|
|
|
private JButton refreshButton;
|
|
private JButton deleteOneButton;
|
|
private JButton revertOneButton;
|
|
private JButton commitOneButton;
|
|
private JButton commitAllButton;
|
|
|
|
private JTextField fulltextField;
|
|
private JTextField spreadsheetQueryField;
|
|
private JTextField orderbyField;
|
|
|
|
/**
|
|
* Creates a list-based spreadsheet editing panel.
|
|
* @param service the spreadsheet service
|
|
* @param listFeedUrl the URL of the list feed
|
|
*/
|
|
public ListBasedSpreadsheetPanel(
|
|
SpreadsheetService service, URL listFeedUrl) {
|
|
this.service = service;
|
|
this.listFeedUrl = listFeedUrl;
|
|
model = new ListTableModel();
|
|
initializeGui();
|
|
}
|
|
|
|
/**
|
|
* Refreshes the contents of the table, applying any queries the user
|
|
* specifies.
|
|
*/
|
|
private void refreshFromServer() {
|
|
try {
|
|
ListQuery query = new ListQuery(listFeedUrl);
|
|
|
|
if (!fulltextField.getText().equals("")) {
|
|
query.setFullTextQuery(fulltextField.getText());
|
|
}
|
|
if (!spreadsheetQueryField.getText().equals("")) {
|
|
query.setSpreadsheetQuery(spreadsheetQueryField.getText());
|
|
}
|
|
if (!orderbyField.getText().equals("")) {
|
|
query.setOrderBy(orderbyField.getText());
|
|
}
|
|
|
|
ListFeed feed = service.query(query, ListFeed.class);
|
|
|
|
model.resetEntries(feed.getEntries());
|
|
} catch (ServiceException e) {
|
|
SpreadsheetApiDemo.showErrorBox(e);
|
|
} catch (IOException e) {
|
|
SpreadsheetApiDemo.showErrorBox(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class models one particular row in the list, tracking both its old
|
|
* contents and its new contents.
|
|
*
|
|
* This is responsible for the "commit/revert/delete" behavior that you
|
|
* see in the GUI.
|
|
*/
|
|
private class ListEntryModel {
|
|
|
|
/**
|
|
* The original entry downloaded.
|
|
* If this is an entry to add, originalEntry will be null.
|
|
*/
|
|
private ListEntry originalEntry = null;
|
|
|
|
/**
|
|
* The new contents to add.
|
|
* If this is not being edited, this will be null.
|
|
*/
|
|
private CustomElementCollection newContents = null;
|
|
|
|
/** Makes an existing row to be edited. */
|
|
public ListEntryModel(ListEntry originalEntry) {
|
|
this.originalEntry = originalEntry;
|
|
}
|
|
|
|
/** Creates a blank row to be edited. */
|
|
public ListEntryModel() {
|
|
}
|
|
|
|
/** Gets the visible contents of a particular column. */
|
|
public String getContents(String column) {
|
|
String result = null;
|
|
|
|
if (newContents != null) {
|
|
result = newContents.getValue(column);
|
|
} else if (originalEntry != null) {
|
|
result = originalEntry.getCustomElements().getValue(column);
|
|
}
|
|
|
|
if (result == null) {
|
|
result = "";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** Gets whether this row neither has data nor is being edited. */
|
|
public boolean isBlank() {
|
|
return originalEntry == null && newContents == null;
|
|
}
|
|
|
|
/** Gets whether this row is oepn for edit. */
|
|
public boolean isBeingEdited() {
|
|
return newContents != null;
|
|
}
|
|
|
|
/** Open the row up for editing. */
|
|
public void startEdit() {
|
|
newContents = new CustomElementCollection();
|
|
if (originalEntry != null) {
|
|
newContents.replaceWithLocal(originalEntry.getCustomElements());
|
|
}
|
|
}
|
|
|
|
/** Sets the contents of a particular cell. */
|
|
public void setContents(String column, String newText) {
|
|
if (!isBeingEdited()) {
|
|
startEdit();
|
|
}
|
|
|
|
newContents.setValueLocal(column, newText);
|
|
}
|
|
|
|
/** Loses all changes. */
|
|
public void revert() {
|
|
newContents = null;
|
|
}
|
|
|
|
/** Actually adds this new entry to the spreadsheet. */
|
|
private void doAddNew() throws ServiceException, IOException {
|
|
ListEntry newEntry = new ListEntry();
|
|
newEntry.getCustomElements().replaceWithLocal(newContents);
|
|
originalEntry = service.insert(listFeedUrl, newEntry);
|
|
newContents = null;
|
|
}
|
|
|
|
/** Actually updates an existing entry on the spreadsheet,
|
|
* checking for edit conflicts. */
|
|
private void doUpdateExisting() throws ServiceException, IOException {
|
|
try {
|
|
originalEntry.getCustomElements().replaceWithLocal(newContents);
|
|
originalEntry = originalEntry.update();
|
|
newContents = null;
|
|
} catch (VersionConflictException e) {
|
|
originalEntry = originalEntry.getSelf();
|
|
TextContent content = (TextContent) originalEntry.getContent();
|
|
JOptionPane.showMessageDialog(null,
|
|
"Someone has edited the row in the meantime to:\n"
|
|
+ originalEntry.getTitle().getPlainText()
|
|
+ " (" + content.getContent().getPlainText() + ")\n"
|
|
+ "Commit again to confirm.",
|
|
"Version Conflict",
|
|
JOptionPane.WARNING_MESSAGE);
|
|
}
|
|
}
|
|
|
|
/** Commits all changes. */
|
|
public void commit() {
|
|
if (isBeingEdited()) {
|
|
boolean success = false;
|
|
|
|
try {
|
|
if (originalEntry != null) {
|
|
doUpdateExisting();
|
|
} else {
|
|
doAddNew();
|
|
}
|
|
|
|
success = true;
|
|
} catch (ServiceException e) {
|
|
SpreadsheetApiDemo.showErrorBox(e);
|
|
} catch (IOException e) {
|
|
SpreadsheetApiDemo.showErrorBox(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Deletes this entry from the backend, but not from the list. */
|
|
public void delete() {
|
|
if (originalEntry != null) {
|
|
try {
|
|
originalEntry.delete();
|
|
} catch (ServiceException e) {
|
|
SpreadsheetApiDemo.showErrorBox(e);
|
|
} catch (IOException e) {
|
|
SpreadsheetApiDemo.showErrorBox(e);
|
|
}
|
|
}
|
|
originalEntry = null;
|
|
newContents = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The Swing model for the spreadsheet.
|
|
*
|
|
* This is mostly GUI code.
|
|
*/
|
|
private class ListTableModel extends AbstractTableModel {
|
|
|
|
/** The name of each column (in the XML). */
|
|
private List<String> columnNames = new ArrayList<String>();
|
|
|
|
/** All entries of the list. */
|
|
private List<ListEntryModel> list = new ArrayList<ListEntryModel>();
|
|
|
|
|
|
/**
|
|
* Resets all the entries from a new list.
|
|
*
|
|
* This also tries to figure out what the set of valid columns is.
|
|
*/
|
|
public synchronized void resetEntries(List<ListEntry> entries) {
|
|
TreeSet<String> columnSet = new TreeSet<String>();
|
|
|
|
list.clear();
|
|
columnNames.clear();
|
|
|
|
for (ListEntry entry : entries) {
|
|
list.add(new ListEntryModel(entry));
|
|
columnSet.addAll(entry.getCustomElements().getTags());
|
|
}
|
|
|
|
// Always have an empty row to edit.
|
|
list.add(new ListEntryModel());
|
|
|
|
columnNames.add("(Edit)");
|
|
columnNames.addAll(columnSet);
|
|
|
|
fireTableStructureChanged();
|
|
fireTableDataChanged();
|
|
}
|
|
|
|
/** Writes back all modified entries to the actual spreadsheet. */
|
|
public synchronized void commitAll() {
|
|
for (ListEntryModel entryModel : list) {
|
|
entryModel.commit();
|
|
}
|
|
fireTableDataChanged();
|
|
}
|
|
|
|
/** Reverts all entries, losing all changes. */
|
|
public synchronized void revertAll() {
|
|
for (ListEntryModel entryModel : list) {
|
|
entryModel.revert();
|
|
}
|
|
fireTableDataChanged();
|
|
}
|
|
|
|
/** Delete one entry by index. */
|
|
public synchronized void deleteOne(int row) {
|
|
if (!list.get(row).isBeingEdited()) {
|
|
list.get(row).delete();
|
|
list.remove(row);
|
|
fireTableRowsDeleted(row, row);
|
|
}
|
|
}
|
|
|
|
/** Commit one entry by index. */
|
|
public synchronized void commitOne(int row) {
|
|
if (row >= 0 && row < list.size()) {
|
|
if (list.get(row).isBeingEdited()) {
|
|
list.get(row).commit();
|
|
fireTableRowsUpdated(row, row);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Revert one entry by index. */
|
|
public synchronized void revertOne(int row) {
|
|
if (list.get(row).isBeingEdited()) {
|
|
list.get(row).revert();
|
|
fireTableRowsUpdated(row, row);
|
|
}
|
|
}
|
|
|
|
/** Gets whether this is the special column. */
|
|
private boolean isSpecialColumn(int col) {
|
|
return col == 0;
|
|
}
|
|
|
|
/**
|
|
* Implements the Swing method for handling cell edits.
|
|
*/
|
|
public synchronized void setValueAt(Object value, int row, int col) {
|
|
ListEntryModel entryModel = list.get(row);
|
|
|
|
if (isSpecialColumn(col)) {
|
|
setRowEditing(row, ((Boolean) value).booleanValue());
|
|
} else {
|
|
setRowEditing(row, true);
|
|
entryModel.setContents(columnNames.get(col), value.toString());
|
|
fireTableCellUpdated(row, col);
|
|
}
|
|
}
|
|
|
|
/** Sets whether a row is being edited. */
|
|
private void setRowEditing(int row, boolean edit) {
|
|
ListEntryModel entryModel = list.get(row);
|
|
|
|
if (edit && !entryModel.isBeingEdited()) {
|
|
if (entryModel.isBlank()) {
|
|
// Always have at least two blank rows.
|
|
list.add(new ListEntryModel());
|
|
fireTableRowsInserted(list.size() - 1, list.size() - 1);
|
|
}
|
|
entryModel.startEdit();
|
|
fireTableRowsUpdated(row, row);
|
|
} else if (!edit) {
|
|
commitOne(row);
|
|
}
|
|
}
|
|
|
|
/** Tells Swing the value at a particular location. */
|
|
public synchronized Object getValueAt(int row, int col) {
|
|
ListEntryModel entryModel = list.get(row);
|
|
|
|
if (isSpecialColumn(col)) {
|
|
return Boolean.valueOf(entryModel.isBeingEdited());
|
|
} else {
|
|
return entryModel.getContents(columnNames.get(col));
|
|
}
|
|
}
|
|
|
|
/** Tells Swing whether the cell is editable. */
|
|
public synchronized boolean isCellEditable(int row, int col) {
|
|
return true;
|
|
}
|
|
|
|
/** Gets the column name by index. */
|
|
public synchronized String getColumnName(int columnIndex) {
|
|
return columnNames.get(columnIndex);
|
|
}
|
|
|
|
/** Gets the column class for editing. */
|
|
public synchronized Class<?> getColumnClass(int columnIndex) {
|
|
if (isSpecialColumn(columnIndex)) {
|
|
return Boolean.class;
|
|
} else {
|
|
return String.class;
|
|
}
|
|
}
|
|
|
|
/** Tells Swing how many rows are in this table. */
|
|
public synchronized int getRowCount() {
|
|
return list.size();
|
|
}
|
|
|
|
/** Tells Swing how many columns are in this table. */
|
|
public synchronized int getColumnCount() {
|
|
return columnNames.size();
|
|
}
|
|
}
|
|
|
|
// GUI code
|
|
|
|
public static JFrame createWindow(SpreadsheetService service,
|
|
URL listFeedUrl) {
|
|
JFrame frame = new JFrame();
|
|
frame.setSize(600, 480);
|
|
frame.getContentPane().add(new ListBasedSpreadsheetPanel(
|
|
service, listFeedUrl));
|
|
frame.setVisible(true);
|
|
frame.setTitle("List Demo - Row-based Editing");
|
|
return frame;
|
|
}
|
|
|
|
private void initializeGui() {
|
|
table = new JTable(model);
|
|
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
|
JScrollPane scrollpane = new JScrollPane(table);
|
|
setLayout(new BorderLayout());
|
|
add(scrollpane, BorderLayout.CENTER);
|
|
|
|
Container southPanel = new JPanel();
|
|
southPanel.setLayout(new GridBagLayout());
|
|
|
|
deleteOneButton = new JButton("Delete");
|
|
deleteOneButton.addActionListener(new ActionHandler());
|
|
southPanel.add(deleteOneButton, getTopConstraints(0));
|
|
revertOneButton = new JButton("Revert");
|
|
revertOneButton.addActionListener(new ActionHandler());
|
|
southPanel.add(revertOneButton, getTopConstraints(1));
|
|
commitOneButton = new JButton("Commit");
|
|
commitOneButton.addActionListener(new ActionHandler());
|
|
southPanel.add(commitOneButton, getTopConstraints(2));
|
|
commitAllButton = new JButton("Commit All");
|
|
commitAllButton.addActionListener(new ActionHandler());
|
|
southPanel.add(commitAllButton, getTopConstraints(3));
|
|
refreshButton = new JButton("Refresh");
|
|
refreshButton.addActionListener(new ActionHandler());
|
|
southPanel.add(refreshButton, getTopConstraints(4));
|
|
|
|
southPanel.add(new JLabel("Full text:"), getLeftConstraints(1));
|
|
fulltextField = new JTextField();
|
|
southPanel.add(fulltextField, getRightConstraints(1));
|
|
|
|
southPanel.add(new JLabel("Structured:"), getLeftConstraints(2));
|
|
spreadsheetQueryField = new JTextField();
|
|
southPanel.add(spreadsheetQueryField, getRightConstraints(2));
|
|
|
|
southPanel.add(new JLabel("Order By:"), getLeftConstraints(3));
|
|
orderbyField = new JTextField();
|
|
southPanel.add(orderbyField, getRightConstraints(3));
|
|
|
|
add(southPanel, BorderLayout.SOUTH);
|
|
|
|
refreshFromServer();
|
|
}
|
|
|
|
private GridBagConstraints getTopConstraints(int col) {
|
|
GridBagConstraints c = new GridBagConstraints();
|
|
|
|
c.gridy = 0;
|
|
c.gridx = col;
|
|
c.fill = GridBagConstraints.HORIZONTAL;
|
|
|
|
return c;
|
|
}
|
|
|
|
private GridBagConstraints getLeftConstraints(int row) {
|
|
GridBagConstraints c = new GridBagConstraints();
|
|
|
|
c.gridy = row;
|
|
c.gridx = 0;
|
|
c.fill = GridBagConstraints.HORIZONTAL;
|
|
|
|
return c;
|
|
}
|
|
|
|
private GridBagConstraints getRightConstraints(int row) {
|
|
GridBagConstraints c = new GridBagConstraints();
|
|
|
|
c.gridy = row;
|
|
c.gridx = 1;
|
|
c.gridwidth = 4;
|
|
c.fill = GridBagConstraints.HORIZONTAL;
|
|
c.weightx = 1.0; // take up as much space as possible
|
|
|
|
return c;
|
|
}
|
|
|
|
private class ActionHandler implements ActionListener {
|
|
public void actionPerformed(ActionEvent ae) {
|
|
if (ae.getSource() == refreshButton) {
|
|
refreshFromServer();
|
|
} else if (ae.getSource() == deleteOneButton) {
|
|
model.deleteOne(table.getSelectedRow());
|
|
} else if (ae.getSource() == revertOneButton) {
|
|
model.revertOne(table.getSelectedRow());
|
|
} else if (ae.getSource() == commitOneButton) {
|
|
model.commitOne(table.getSelectedRow());
|
|
} else if (ae.getSource() == commitAllButton) {
|
|
model.commitAll();
|
|
}
|
|
}
|
|
}
|
|
}
|