Revision: 3792
Author: [email protected]
Date: Tue Jul 27 14:11:45 2010
Log: Added the first progress bar for persisting to the server. This is
really just a background thread that updates the progress bar until the
server responds with the persist calls complete. There is no feedback from
the server updating the numbers, it is just an approximation based on
previous runs of persist calls that have made it to the server.
The next step is to make the server respond with progress in saving
information to the server. We also need to make other users persist calls
be noticed by the progress bars to notify users when changes will be
incoming.
http://code.google.com/p/power-architect/source/detail?r=3792
Added:
/trunk/src/main/java/ca/sqlpower/architect/ArchitectStatusInformation.java
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectStatusBar.java
/trunk/src/main/resources/icons/progressBar.png
Modified:
/trunk/regress/ca/sqlpower/architect/StubArchitectSession.java
/trunk/regress/ca/sqlpower/architect/TestingArchitectSession.java
/trunk/regress/ca/sqlpower/architect/swingui/TestingArchitectSwingSession.java
/trunk/src/main/java/ca/sqlpower/architect/ArchitectSession.java
/trunk/src/main/java/ca/sqlpower/architect/ArchitectSessionImpl.java
/trunk/src/main/java/ca/sqlpower/architect/enterprise/NetworkConflictResolver.java
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectFrame.java
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectSwingSession.java
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectSwingSessionImpl.java
=======================================
--- /dev/null
+++
/trunk/src/main/java/ca/sqlpower/architect/ArchitectStatusInformation.java
Tue Jul 27 14:11:45 2010
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010, SQL Power Group Inc.
+ *
+ * This file is part of SQL Power Architect.
+ *
+ * SQL Power Architect 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SQL Power Architect 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ca.sqlpower.architect;
+
+import ca.sqlpower.util.MonitorableImpl;
+
+/**
+ * Used to update a status message/label/image/text/other that is user
visible
+ * but does not interrupt their work.
+ */
+public interface ArchitectStatusInformation {
+
+ /**
+ * Creates and returns an object that can be used to monitor the
progress of
+ * one type of action. If you want to monitor a second type of action
call
+ * this method again to get a new monitor for the new action. This will
+ * prevent the progress bars from moving backwards. When the monitor
+ * isFinished method returns true or the progress has reached or
passed the
+ * job size the monitor will be removed.
+ */
+ public MonitorableImpl createProgressMonitor();
+
+}
=======================================
--- /dev/null
+++
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectStatusBar.java
Tue Jul 27 14:11:45 2010
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2010, SQL Power Group Inc.
+ *
+ * This file is part of SQL Power Architect.
+ *
+ * SQL Power Architect 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SQL Power Architect 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ca.sqlpower.architect.swingui;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+
+import ca.sqlpower.architect.ArchitectStatusInformation;
+import ca.sqlpower.util.MonitorableImpl;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+
+/**
+ * This class contains a status bar that appears at the bottom of the
+ * ArchitectFrame. The status bar is handy for updating the user on
changes that
+ * are helpful for the user to know but is not so important that they need
to be
+ * given a pop-up.
+ */
+public class ArchitectStatusBar implements ArchitectStatusInformation {
+
+ private static final Logger logger =
Logger.getLogger(ArchitectStatusBar.class);
+
+ private static final ImageIcon PROGRESS_BAR_ICON = new
ImageIcon(ArchitectStatusBar.class.getResource("/icons/progressBar.png"));
+
+ /**
+ * This progress bar updates its UI immediately by painting directly
to its
+ * graphics object on changes to its settings to keep the user
informed of
+ * the current progress of the operation even if the foreground thread
that
+ * normally does the painting is blocked waiting for server operations.
+ * <p>
+ * Methods in this class are synchronized as the progress bar can be
updated
+ * from multiple threads.
+ */
+ private class ArchitectStatusProgressBar extends MonitorableImpl {
+
+ /**
+ * This is the starting x position on the progress bar where this
+ * progress bar specifically can start to paint.
+ */
+ private int x = 0;
+
+ /**
+ * This is the width of the progress bar this specific progress bar
+ * can update on.
+ */
+ private int width = 0;
+
+ @Override
+ public synchronized void setJobSize(Integer jobSize) {
+ super.setJobSize(jobSize);
+ paint();
+ }
+
+ @Override
+ public synchronized void setMessage(String message) {
+ super.setMessage(message);
+ paint();
+ }
+
+ @Override
+ public synchronized void setProgress(int progress) {
+ super.setProgress(progress);
+ paint();
+ if (progress >= getJobSize()) {
+ removeProgressBar(this);
+ }
+ }
+
+ @Override
+ public synchronized void incrementProgress() {
+ super.incrementProgress();
+ paint();
+ if (getProgress() >= getJobSize()) {
+ removeProgressBar(this);
+ }
+ }
+
+ @Override
+ public synchronized void setCancelled(boolean cancelled) {
+ super.setCancelled(cancelled);
+ paint();
+ removeProgressBar(this);
+ }
+
+ @Override
+ public synchronized void setFinished(boolean finished) {
+ super.setFinished(finished);
+ paint();
+ removeProgressBar(this);
+ }
+
+ private synchronized void paint() {
+ Graphics g = progressBarPanel.getGraphics();
+ progressBarPanel.setDoubleBuffered(true);
+
+ if (!isCancelled() && !isFinished() &&
+ getJobSize() != null && getProgress() < getJobSize()) {
+ BufferedImage buffer = new BufferedImage(getWidth(),
progressBarPanel.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics bufferG = buffer.getGraphics();
+ bufferG = progressBarPanel.getGraphics();
+
+ g.setColor(progressBarPanel.getBackground());
+ g.fillRect(getX(), 0, getWidth(),
progressBarPanel.getHeight());
+ bufferG.drawImage(PROGRESS_BAR_ICON.getImage(), getX(), 0,
+ (int) (getWidth() * getProgress() / getJobSize()),
+ progressBarPanel.getHeight(), null);
+
+ bufferG.setColor(Color.BLACK);
+ if (getMessage() != null) {
+
+ Font font = g.getFont();
+ int fontSize = font.getSize();
+ FontMetrics fm = g.getFontMetrics();
+ while (fm.getHeight() > progressBarPanel.getHeight()
&& fontSize > 0) {
+ font = font.deriveFont((float) (fontSize - 1));
+ g.setFont(font);
+ fm = g.getFontMetrics();
+ fontSize--;
+ }
+
+ int fontX = (int) ((getWidth() / 2) -
(fm.getStringBounds(getMessage(), g).getWidth() / 2)) + getX();
+ int fontY = (progressBarPanel.getHeight() / 2) +
(fm.getHeight() / 2);
+ bufferG.drawString(getMessage(), fontX, fontY);
+ }
+ g.drawImage(buffer, getWidth(),
progressBarPanel.getHeight(), null);
+ } else {
+ g.setColor(progressBarPanel.getBackground());
+ g.fillRect(getX(), 0, getWidth(),
progressBarPanel.getHeight());
+ }
+ }
+
+ public synchronized void setX(int x) {
+ this.x = x;
+ paint();
+ }
+
+ public synchronized int getX() {
+ return x;
+ }
+
+ public synchronized void setWidth(int width) {
+ this.width = width;
+ paint();
+ }
+
+ public synchronized int getWidth() {
+ return width;
+ }
+
+ }
+
+ /**
+ * The actual panel that appears at the bottom of the ArchitectFrame.
+ * Interesting things like status text and progress bars can be entered
+ * here.
+ */
+ private final JPanel statusBar = new JPanel();
+
+ /**
+ * Different {...@link ArchitectStatusBar} classes will be given this
progress
+ * bar and a section of it that they can repaint for a progress bar.
This
+ * allows the progress to be given to the user even if the EDT thread
is
+ * busy. If we tried to add in other panels and work with them the
panel
+ * would not be added until after the current work of the EDT was
completed
+ * which may be too late.
+ */
+ private final JPanel progressBarPanel = new JPanel();
+
+ /**
+ * The existing progress bars in this status bar.
+ */
+ private final List<ArchitectStatusProgressBar> progressBars =
+ new ArrayList<ArchitectStatusProgressBar>();
+
+ public ArchitectStatusBar() {
+ DefaultFormBuilder builder = new DefaultFormBuilder(new
FormLayout("fill:pref:grow"), statusBar);
+ builder.append(progressBarPanel);
+ }
+
+ public JPanel getStatusBar() {
+ return statusBar;
+ }
+
+ @Override
+ public MonitorableImpl createProgressMonitor() {
+ ArchitectStatusProgressBar newBar = new
ArchitectStatusProgressBar();
+ progressBars.add(newBar);
+ resizeProgressBars();
+ return newBar;
+ }
+
+ /**
+ * Call to clear a progress bar off of the status bar.
+ */
+ private void removeProgressBar(ArchitectStatusProgressBar bar) {
+ progressBars.remove(bar);
+ resizeProgressBars();
+ }
+
+ private void resizeProgressBars() {
+ Graphics g = progressBarPanel.getGraphics();
+ g.setColor(progressBarPanel.getBackground());
+ g.fillRect(0, 0, progressBarPanel.getWidth(),
progressBarPanel.getHeight());
+ if (progressBars.isEmpty()) return;
+ int newWidth = progressBarPanel.getWidth() / progressBars.size();
+ int x = 0;
+ for (ArchitectStatusProgressBar bar : progressBars) {
+ bar.setX(x);
+ bar.setWidth(newWidth);
+ x += newWidth;
+ }
+ }
+
+}
=======================================
--- /dev/null
+++ /trunk/src/main/resources/icons/progressBar.png Tue Jul 27 14:11:45 2010
Binary file, no diff available.
=======================================
--- /trunk/regress/ca/sqlpower/architect/StubArchitectSession.java Thu Jul
22 13:35:26 2010
+++ /trunk/regress/ca/sqlpower/architect/StubArchitectSession.java Tue Jul
27 14:11:45 2010
@@ -187,4 +187,10 @@
public void setLiquibaseSettings(LiquibaseSettings settings) {
}
-}
+
+ @Override
+ public ArchitectStatusInformation getStatusInformation() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
=======================================
--- /trunk/regress/ca/sqlpower/architect/TestingArchitectSession.java Thu
Jul 22 13:35:26 2010
+++ /trunk/regress/ca/sqlpower/architect/TestingArchitectSession.java Tue
Jul 27 14:11:45 2010
@@ -223,4 +223,10 @@
public void setLiquibaseSettings(LiquibaseSettings settings) {
}
-}
+
+ @Override
+ public ArchitectStatusInformation getStatusInformation() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
=======================================
---
/trunk/regress/ca/sqlpower/architect/swingui/TestingArchitectSwingSession.java
Thu Jul 22 13:35:26 2010
+++
/trunk/regress/ca/sqlpower/architect/swingui/TestingArchitectSwingSession.java
Tue Jul 27 14:11:45 2010
@@ -518,4 +518,9 @@
// TODO Auto-generated method stub
}
-}
+
+ @Override
+ public ArchitectStatusBar getStatusInformation() {
+ return null;
+ }
+}
=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/ArchitectSession.java Thu
Jul 22 13:35:26 2010
+++ /trunk/src/main/java/ca/sqlpower/architect/ArchitectSession.java Tue
Jul 27 14:11:45 2010
@@ -185,4 +185,11 @@
*/
public void setLiquibaseSettings(LiquibaseSettings settings);
-}
+ /**
+ * Returns the status information object to update the user on
different
+ * kinds of progress. May return null if no status information exists
(for
+ * headless mode).
+ */
+ public ArchitectStatusInformation getStatusInformation();
+
+}
=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/ArchitectSessionImpl.java
Tue Jul 27 09:04:00 2010
+++ /trunk/src/main/java/ca/sqlpower/architect/ArchitectSessionImpl.java
Tue Jul 27 14:11:45 2010
@@ -84,6 +84,8 @@
private LiquibaseSettings liquibaseSettings;
protected boolean isEnterpriseSession;
+
+ private ArchitectStatusInformation statusInfo;
public ArchitectSessionImpl(final ArchitectSessionContext context,
String name) throws SQLObjectException {
@@ -291,5 +293,14 @@
public void setLiquibaseSettings(LiquibaseSettings settings) {
liquibaseSettings = settings;
}
+
+ @Override
+ public ArchitectStatusInformation getStatusInformation() {
+ return statusInfo;
+ }
+
+ public void setStatusInfo(ArchitectStatusInformation statusInfo) {
+ this.statusInfo = statusInfo;
+ }
}
=======================================
---
/trunk/src/main/java/ca/sqlpower/architect/enterprise/NetworkConflictResolver.java
Fri Jul 16 08:41:08 2010
+++
/trunk/src/main/java/ca/sqlpower/architect/enterprise/NetworkConflictResolver.java
Tue Jul 27 14:11:45 2010
@@ -55,6 +55,7 @@
import ca.sqlpower.dao.session.SessionPersisterSuperConverter;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.sqlobject.SQLRelationship.ColumnMapping;
+import ca.sqlpower.util.MonitorableImpl;
import ca.sqlpower.util.SQLPowerUtils;
import ca.sqlpower.util.UserPrompterFactory;
import ca.sqlpower.util.UserPrompter.UserPromptOptions;
@@ -75,6 +76,13 @@
*/
private static final int MAX_CONFLICTS_TO_DISPLAY = 10;
+ /**
+ * This is currently a static average wait time for how long each
change to
+ * the server will take. This will let the progress bar update at a
decent
+ * rate.
+ */
+ private static final int AVG_WAIT_TIME_FOR_PERSIST = 12;
+
private static final Logger logger =
Logger.getLogger(NetworkConflictResolver.class);
private AtomicBoolean postingJSON = new AtomicBoolean(false);
private boolean updating = false;
@@ -88,6 +96,14 @@
private int currentRevision = 0;
private long retryDelay = 1000;
+
+ /**
+ * This double will store and be updated with the average wait time
for each
+ * persist calls so the progress of the progress bar is on average
correct.
+ * At some point we may want the server to respond with the actual
progress
+ * but this is a start.
+ */
+ private double currentWaitPerPersist = AVG_WAIT_TIME_FOR_PERSIST;
private final SPJSONMessageDecoder jsonDecoder;
@@ -157,8 +173,36 @@
if (postingJSON.get() && !reflush) {
return;
}
+ MonitorableImpl monitor = null;
+ long startTimeMillis = System.currentTimeMillis();
try {
postingJSON.set(true);
+
+ // Start a progress bar to update the user with the current
changes.
+ if (session.getStatusInformation() != null) {
+ monitor =
session.getStatusInformation().createProgressMonitor();
+ monitor.setJobSize(messageBuffer.length() + 2);
+ monitor.setMessage("Writing " + messageBuffer.length() + "
changes to the server.");
+ monitor.setProgress(0);
+ final MonitorableImpl finalMonitor = monitor;
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (finalMonitor.getProgress() <
finalMonitor.getJobSize()) {
+ try {
+ Thread.sleep((long) currentWaitPerPersist);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ finalMonitor.incrementProgress();
+ finalMonitor.setMessage("Working on " +
finalMonitor.getProgress() +
+ " of " + finalMonitor.getJobSize());
+ }
+ finalMonitor.setMessage("Completing server
update.");
+ }
+ }).start();
+ }
+
// Try to send json message ...
JSONMessage response = null;
try {
@@ -191,6 +235,9 @@
} catch (JSONException e) {
throw new RuntimeException("Could not update current
revision" + e.getMessage());
}
+ long endTime = System.currentTimeMillis();
+ double processTimePerObj = (endTime - startTimeMillis) /
messageBuffer.length();
+ currentWaitPerPersist = currentWaitPerPersist * 0.9 +
processTimePerObj * 0.1;
} else {
// Did not successfully post json, we must update
ourselves, and then try again if we can.
if (!reflush) {
@@ -264,6 +311,9 @@
}
}
} finally {
+ if (monitor != null) {
+ monitor.setFinished(true);
+ }
postingJSON.set(false);
clear(true);
}
@@ -513,7 +563,7 @@
private Set<String> getColumnMappingChanges(Multimap<String,
PersistedSPOProperty> properties) {
Set<String> changes = new HashSet<String>();
for (String uuid : properties.keySet()) {
- Class type;
+ Class<?> type;
String parentId;
SPObject spo = session.getWorkspace().getObjectInTree(uuid);
PersistedSPObject o = outboundObjectsToAdd.get(uuid);
=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectFrame.java
Thu Jul 22 13:35:26 2010
+++ /trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectFrame.java
Tue Jul 27 14:11:45 2010
@@ -256,6 +256,11 @@
private CutSelectedAction cutAction;
private PasteSelectedAction pasteAction;
+ /**
+ * A status bar that can have its content changed to useful messages.
+ */
+ private final ArchitectStatusBar statusBar = new ArchitectStatusBar();
+
private RefreshProjectAction refreshProjectAction;
private List<SelectionListener> selectionListeners = new
ArrayList<SelectionListener>();
@@ -838,6 +843,8 @@
cp.add(splitPane, BorderLayout.CENTER);
logger.debug("Added splitpane to content pane"); //$NON-NLS-1$
+ projectBarPane.add(statusBar.getStatusBar(), BorderLayout.SOUTH);
+
stackedTabPane.setSelectedIndex(0);
context.registerFrame(this);
@@ -1575,6 +1582,10 @@
public EditCriticSettingsAction getShowCriticsManagerAction() {
return showCriticsManagerAction;
}
+
+ public ArchitectStatusBar getStatusBar() {
+ return statusBar;
+ }
private class TabDropTargetListener implements DropTargetListener {
=======================================
---
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectSwingSession.java
Mon Jul 12 08:21:11 2010
+++
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectSwingSession.java
Tue Jul 27 14:11:45 2010
@@ -341,4 +341,6 @@
public JScrollPane getPlayPenScrollPane();
void setPlayPenScrollPane(JScrollPane ppScrollPane);
-}
+
+ ArchitectStatusBar getStatusInformation();
+}
=======================================
---
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectSwingSessionImpl.java
Thu Jul 22 13:35:26 2010
+++
/trunk/src/main/java/ca/sqlpower/architect/swingui/ArchitectSwingSessionImpl.java
Tue Jul 27 14:11:45 2010
@@ -396,6 +396,8 @@
}
this.frame = parentFrame;
+
+
((ArchitectSessionImpl)delegateSession).setStatusInfo(getStatusInformation());
// makes the tool tips show up on these components
ToolTipManager.sharedInstance().registerComponent(playPen);
@@ -1253,5 +1255,11 @@
public void setProjectPanel(JComponent panel) {
projectPanel = panel;
}
+
+ @Override
+ public ArchitectStatusBar getStatusInformation() {
+ if (frame == null || frame.getStatusBar() == null) return null;
+ return frame.getStatusBar();
+ }
}