1 | // Jomic - a viewer for comic book archives. |
2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | package net.sf.jomic.tools; |
17 | |
18 | import java.awt.Container; |
19 | import java.awt.Dimension; |
20 | import java.awt.GridBagConstraints; |
21 | import java.awt.GridBagLayout; |
22 | import java.awt.Insets; |
23 | import java.awt.event.ActionEvent; |
24 | import java.awt.event.ActionListener; |
25 | import java.awt.event.WindowEvent; |
26 | import java.awt.event.WindowListener; |
27 | |
28 | import javax.swing.AbstractAction; |
29 | import javax.swing.Action; |
30 | import javax.swing.JButton; |
31 | import javax.swing.JFrame; |
32 | import javax.swing.JLabel; |
33 | import javax.swing.JProgressBar; |
34 | |
35 | import net.sf.jomic.common.JomicTools; |
36 | import net.sf.jomic.ui.Commands; |
37 | |
38 | import org.apache.commons.logging.Log; |
39 | import org.apache.commons.logging.LogFactory; |
40 | |
41 | /** |
42 | * JFrame containing a label to display a note on the status, a progress bar and a "Cancel" button. |
43 | * Different to <code>ProgressMonitor</code>, this can be reused after calling <code>reset()</code> |
44 | * . Furthermore, the progress values are of type <code>long</code> instead of <code>int</code>. |
45 | * |
46 | * @author Thomas Aglassinger |
47 | * @see javax.swing.ProgressMonitor |
48 | */ |
49 | public class ProgressFrame extends JFrame implements ActionListener, WindowListener |
50 | { |
51 | private static final int INSET = 6; |
52 | |
53 | private ActionDelegate actionDelegate; |
54 | private JButton cancelButton; |
55 | private boolean canceled; |
56 | private JomicTools jomicTools; |
57 | private LocaleTools localeTools; |
58 | private Log logger; |
59 | private long maxProgress; |
60 | private JLabel noteLabel; |
61 | private JProgressBar progressBar; |
62 | private SystemTools systemTools; |
63 | private UiTools uiTools; |
64 | |
65 | public ProgressFrame() { |
66 | super(); |
67 | logger = LogFactory.getLog(ProgressFrame.class); |
68 | jomicTools = JomicTools.instance(); |
69 | localeTools = LocaleTools.instance(); |
70 | systemTools = SystemTools.instance(); |
71 | uiTools = UiTools.instance(); |
72 | |
73 | String cancelText = localeTools.getCancelText(); |
74 | Container pane = getContentPane(); |
75 | GridBagConstraints constraints = new GridBagConstraints(); |
76 | Action cancelAction = new CancelAction(this); |
77 | |
78 | jomicTools.setIconToJomicLogo(this); |
79 | actionDelegate = new ActionDelegate(logger); |
80 | noteLabel = new JLabel("..."); |
81 | progressBar = new JProgressBar(); |
82 | cancelButton = new JButton(); |
83 | cancelButton.setAction(cancelAction); |
84 | cancelButton.addActionListener(this); |
85 | uiTools.addKeyStrokeAction(cancelButton, "CANCEL", cancelAction); |
86 | uiTools.addKeyStrokeAction(cancelButton, "ESCAPE", cancelAction); |
87 | if (systemTools.isMacOSX()) { |
88 | uiTools.addKeyStrokeAction(cancelButton, "meta PERIOD", cancelAction); |
89 | } |
90 | cancelButton.setText(cancelText); |
91 | getRootPane().setDefaultButton(cancelButton); |
92 | |
93 | Dimension size = progressBar.getPreferredSize(); |
94 | |
95 | progressBar.setPreferredSize(new Dimension(2 * size.width, size.height)); |
96 | |
97 | // TODO: Use insets conforming to UI guidelines. |
98 | pane.setLayout(new GridBagLayout()); |
99 | constraints.anchor = GridBagConstraints.LINE_START; |
100 | constraints.gridwidth = GridBagConstraints.REMAINDER; |
101 | constraints.gridx = 0; |
102 | constraints.gridy = 0; |
103 | constraints.insets = new Insets(INSET, INSET + INSET / 2, INSET, INSET); |
104 | pane.add(noteLabel, constraints); |
105 | constraints.anchor = GridBagConstraints.CENTER; |
106 | constraints.gridwidth = 1; |
107 | constraints.gridy += 1; |
108 | constraints.insets = new Insets(0, INSET, 0, INSET); |
109 | pane.add(progressBar, constraints); |
110 | constraints.gridx += 1; |
111 | constraints.insets = new Insets(0, 0, INSET, INSET); |
112 | pane.add(cancelButton, constraints); |
113 | reset(); |
114 | pack(); |
115 | setResizable(false); |
116 | } |
117 | |
118 | /** |
119 | * Specifies the maximum value. |
120 | */ |
121 | public void setMaximum(long newMaximumValue) { |
122 | assert newMaximumValue > 0; |
123 | progressBar.setMaximum(getNormalizedProgress(newMaximumValue, newMaximumValue)); |
124 | maxProgress = newMaximumValue; |
125 | } |
126 | |
127 | /** |
128 | * Specifies the additional note that is displayed along with the progress message. Used, for |
129 | * example, to show which file the is currently being copied during a multiple-file copy. |
130 | * |
131 | * @param newNote a String specifying the note to display |
132 | */ |
133 | public void setNote(String newNote) { |
134 | noteLabel.setText(newNote); |
135 | validate(); |
136 | } |
137 | |
138 | /** |
139 | * Indicate the progress of the operation being monitored. |
140 | * |
141 | * @param newProgress an int specifying the current value, between the maximum and minimum |
142 | * specified for this component |
143 | */ |
144 | public void setProgress(long newProgress) { |
145 | progressBar.setIndeterminate(false); |
146 | progressBar.setValue(getNormalizedProgress(newProgress, maxProgress)); |
147 | } |
148 | |
149 | public boolean isCanceled() { |
150 | return canceled; |
151 | } |
152 | |
153 | /** |
154 | * Get int-progress from long currentValue normalized in relation to maxValue. |
155 | */ |
156 | private int getNormalizedProgress(long currentValue, long maxValue) { |
157 | assert maxValue > 0; |
158 | double value = (double) currentValue / maxValue; |
159 | |
160 | return (int) (value * (Integer.MAX_VALUE / 2)); |
161 | } |
162 | |
163 | public void actionPerformed(ActionEvent event) { |
164 | Object eventSource = event.getSource(); |
165 | |
166 | assert eventSource == cancelButton : "event.source=" + eventSource; |
167 | cancel(); |
168 | actionDelegate.actionPerformed(event); |
169 | } |
170 | |
171 | public void addActionListener(ActionListener listener) { |
172 | actionDelegate.addActionListener(listener); |
173 | } |
174 | |
175 | public void dispose() { |
176 | if (logger.isDebugEnabled()) { |
177 | logger.debug("disposing " + ProgressFrame.class); |
178 | } |
179 | cancelButton.removeActionListener(this); |
180 | // HACK: without this, the AWTEventThread would continue to exist. |
181 | // I have no idea why. |
182 | progressBar.setIndeterminate(false); |
183 | super.dispose(); |
184 | } |
185 | |
186 | public void removeActionListener(ActionListener listener) { |
187 | actionDelegate.removeActionListener(listener); |
188 | } |
189 | |
190 | public synchronized void reset() { |
191 | canceled = false; |
192 | noteLabel.setText(" "); |
193 | cancelButton.setEnabled(true); |
194 | progressBar.setValue(progressBar.getMinimum()); |
195 | progressBar.setIndeterminate(true); |
196 | } |
197 | |
198 | public void windowActivated(WindowEvent event) { |
199 | // Do nothing. |
200 | } |
201 | |
202 | public void windowClosed(WindowEvent event) { |
203 | // Do nothing. |
204 | } |
205 | |
206 | public void windowClosing(WindowEvent event) { |
207 | cancel(); |
208 | } |
209 | |
210 | public void windowDeactivated(WindowEvent event) { |
211 | // Do nothing. |
212 | } |
213 | |
214 | public void windowDeiconified(WindowEvent event) { |
215 | // Do nothing. |
216 | } |
217 | |
218 | public void windowIconified(WindowEvent event) { |
219 | // Do nothing. |
220 | } |
221 | |
222 | public void windowOpened(WindowEvent event) { |
223 | // Do nothing. |
224 | } |
225 | |
226 | private synchronized void cancel() { |
227 | if (!canceled) { |
228 | cancelButton.setEnabled(false); |
229 | canceled = true; |
230 | if (logger.isInfoEnabled()) { |
231 | logger.info("operation canceled"); |
232 | } |
233 | } |
234 | } |
235 | |
236 | /** |
237 | * Action to cancel a progress frame. |
238 | * |
239 | * @author Thomas Aglassinger |
240 | */ |
241 | private class CancelAction extends AbstractAction |
242 | { |
243 | private ProgressFrame owner; |
244 | |
245 | public CancelAction(ProgressFrame newOwner) { |
246 | super(Commands.CANCEL); |
247 | assert newOwner != null; |
248 | owner = newOwner; |
249 | } |
250 | |
251 | public void actionPerformed(ActionEvent event) { |
252 | owner.cancel(); |
253 | } |
254 | } |
255 | } |