1 package com.bonevich.util;
2
3 import java.awt.*;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.awt.event.MouseEvent;
7 import java.io.*;
8 import java.util.ArrayList;
9
10 import javax.swing.*;
11
12 /***
13 * Provide a file history mechanism for the File menu of a parent frame.
14 * <p>
15 * Borrowed heavily from:
16 * <a href="http://www.javaworld.com/javaworld/javatips/jw-javatip119.html">Java
17 * Tip 119: Don't know much about file history?"</a>,
18 * by Klaus Berg, JavaWorld, 2 November 2001
19 *
20 * @author Jeffrey Bonevich
21 * @since JDK 1.2
22 */
23 public final class FileHistory
24 {
25 //////////////////////////////////////////////////////////
26 // Constants
27 private static final int MAX_ITEM_LEN = 50;
28 private static final String FILE_SEPARATOR_STR = System.getProperty("file.separator");
29
30 //////////////////////////////////////////////////////////
31 // Attributes
32 private static String _historyFile;
33 private static int _maxItemnames;
34 private static ArrayList _itemnameHistory = new ArrayList(_maxItemnames);
35 private static ArrayList _pathnameHistory = new ArrayList(_maxItemnames);
36
37 private FileHistoryOwner _owner;
38 private JMenu _fileMenu;
39
40 //////////////////////////////////////////////////////////
41 // Constructors
42 public FileHistory(FileHistoryOwner owner)
43 {
44 _owner = owner;
45 _historyFile =
46 System.getProperty("user.home")
47 + FILE_SEPARATOR_STR
48 + "." + _owner.getApplicationName()
49 + ".cfg";
50
51 String maxItemnamesStr = System.getProperty("itemnames.max", "9");
52 try
53 {
54 _maxItemnames = Integer.parseInt(maxItemnamesStr);
55 }
56 catch (NumberFormatException e)
57 {
58 e.printStackTrace();
59 _maxItemnames = 9;
60 }
61 if (_maxItemnames < 1)
62 {
63 _maxItemnames = 9;
64 }
65
66 _fileMenu = _owner.getFileMenu();
67 }
68
69 //////////////////////////////////////////////////////////
70 // Operations
71 /***
72 * Initialize the file item and path lists from a configuration
73 * file and build up the additional entries in the File menu.
74 */
75 public void initFileMenuHistory()
76 {
77 if (new File(_historyFile).exists())
78 {
79 try
80 {
81 FileInputStream fis = new FileInputStream(_historyFile);
82 ObjectInputStream ois = new ObjectInputStream(fis);
83 int itemnameCount = ois.readInt();
84 // if the user has reduced filehistory.size in the past: cut last items
85 if (itemnameCount > _maxItemnames)
86 {
87 itemnameCount = _maxItemnames;
88 }
89 if (itemnameCount > 0)
90 {
91 _fileMenu.addSeparator();
92 }
93 for (int i = 0; i < itemnameCount; i++)
94 {
95 _itemnameHistory.add((String)ois.readObject());
96 _pathnameHistory.add((String)ois.readObject());
97
98 MenuItemWithFixedTooltip item =
99 new MenuItemWithFixedTooltip(i+1, (String) _itemnameHistory.get(i));
100 item.setToolTipText((String) _pathnameHistory.get(i));
101 item.addActionListener(new ItemListener(i));
102 _fileMenu.add(item);
103 }
104
105 ois.close();
106 fis.close();
107 }
108 catch (Exception e)
109 {
110 System.err.println("Trouble reading file history entries: " + e);
111 e.printStackTrace();
112 }
113 }
114 }
115
116 /***
117 * Save the lists of file items and paths.
118 */
119 public void saveHistoryEntries()
120 {
121 try
122 {
123 FileOutputStream fos = new FileOutputStream(_historyFile);
124 ObjectOutputStream oos = new ObjectOutputStream(fos);
125 int itemnameCount = _itemnameHistory.size();
126 oos.writeInt(itemnameCount);
127 for (int i = 0; i < itemnameCount; i++)
128 {
129 oos.writeObject((String) _itemnameHistory.get(i));
130 oos.writeObject((String) _pathnameHistory.get(i));
131 }
132 oos.flush();
133 oos.close();
134 fos.close();
135 }
136 catch (Exception e)
137 {
138 System.err.println("Trouble saving file history entries: " + e);
139 e.printStackTrace();
140 }
141 }
142
143 /***
144 * Insert the last loaded pathname into the File menu if it is not
145 * present yet. Only max pathnames are shown (the max number can be
146 * set in Jmon.ini; default is 9). Every item starts with
147 * "<i>: ", where <i> is in the range [1..max].
148 * The loaded itemname will become item number 1 in the list.
149 */
150 public final void insertPathname(String pathname)
151 {
152 for (int k = 0; k < _pathnameHistory.size(); k++)
153 {
154 if (((String) _pathnameHistory.get(k)).equals(pathname))
155 {
156 int index = _fileMenu.getItemCount() - _itemnameHistory.size() + k;
157 _fileMenu.remove(index);
158 _pathnameHistory.remove(k);
159 _itemnameHistory.remove(k);
160 if (_itemnameHistory.isEmpty())
161 {
162 //JSeparator is the last menu item at (index - 1)
163 _fileMenu.remove(index - 1);
164 }
165 insertPathname(pathname);
166 return;
167 }
168 }
169
170 if (_itemnameHistory.isEmpty())
171 {
172 _fileMenu.addSeparator();
173 }
174 else
175 {
176 // remove all itemname entries to prepare for re-arrangement
177 for (int i = _fileMenu.getItemCount() - 1, j = 0; j < _itemnameHistory.size(); i--, j++)
178 {
179 _fileMenu.remove(i);
180 }
181 }
182
183 if (_itemnameHistory.size() == _maxItemnames)
184 {
185 // fileList is full: remove last entry to get space for the first item
186 _itemnameHistory.remove(_maxItemnames - 1);
187 _pathnameHistory.remove(_maxItemnames - 1);
188 }
189
190 _itemnameHistory.add(0, getItemname(pathname));
191 _pathnameHistory.add(0, pathname);
192
193 for (int i = 0; i < _itemnameHistory.size(); i++)
194 {
195 MenuItemWithFixedTooltip item =
196 new MenuItemWithFixedTooltip(i+1, (String) _itemnameHistory.get(i));
197 item.setToolTipText((String) _pathnameHistory.get(i));
198 item.addActionListener(new ItemListener(i));
199 _fileMenu.add(item);
200 }
201 }
202
203 /***
204 * Process the file history list that is appended to the file menu:
205 * display a dialog to delete itemname items.
206 */
207 public void processList()
208 {
209 final JList itemList = createItemList();
210 final JDialog dialog = new JDialog(_owner.getFrame(), "File History", true);
211 dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
212 Container c = dialog.getContentPane();
213 JScrollPane scroller = new JScrollPane(itemList);
214 scroller.setBorder(
215 BorderFactory.createCompoundBorder(
216 BorderFactory.createEmptyBorder(10, 10, 10, 10),
217 BorderFactory.createLoweredBevelBorder()
218 )
219 );
220 c.add(scroller, BorderLayout.CENTER);
221
222 JButton deleteB = new JButton("Delete");
223 deleteB.addActionListener(
224 new ActionListener()
225 {
226 public void actionPerformed(ActionEvent e)
227 {
228 int[] indicesToDelete = itemList.getSelectedIndices();
229 if (indicesToDelete.length > 0)
230 {
231 int oldFileHistorySize = _itemnameHistory.size();
232 ArrayList itemnames = new ArrayList(oldFileHistorySize - indicesToDelete.length);
233 ArrayList pathnames = new ArrayList(oldFileHistorySize - indicesToDelete.length);
234 for (int i=0; i<oldFileHistorySize; i++)
235 {
236 boolean copyItem = true;
237 for (int j = 0; j < indicesToDelete.length; j++)
238 {
239 if (i == indicesToDelete[j])
240 {
241 copyItem = false;
242 break;
243 }
244 }
245 if (copyItem)
246 {
247 itemnames.add(_itemnameHistory.get(i));
248 pathnames.add(_pathnameHistory.get(i));
249 }
250 }
251 _itemnameHistory = itemnames;
252 _pathnameHistory = pathnames;
253 itemList.revalidate();
254 itemList.repaint();
255 // re-arrange file menu history
256 for (int i = _fileMenu.getItemCount() - 1, j = 0; j < oldFileHistorySize; i--, j++)
257 {
258 _fileMenu.remove(i);
259 }
260 int lastIndex = _fileMenu.getItemCount() - 1;
261 for (int i = 0; i < _itemnameHistory.size(); i++)
262 {
263 MenuItemWithFixedTooltip item =
264 new MenuItemWithFixedTooltip(i+1, (String) _itemnameHistory.get(i));
265 item.setToolTipText((String) _pathnameHistory.get(i));
266 item.addActionListener(new ItemListener(i));
267 _fileMenu.add(item);
268 }
269 if (_itemnameHistory.isEmpty())
270 {
271 _fileMenu.remove(lastIndex); // no items were added: remove JSeparator too
272 }
273 }
274 }
275 }
276 );
277
278 JButton closeB = new JButton("Close");
279 closeB.setMaximumSize(deleteB.getPreferredSize());
280 closeB.addActionListener(
281 new ActionListener()
282 {
283 public void actionPerformed(ActionEvent e)
284 {
285 dialog.hide();
286 dialog.dispose();
287 }
288 }
289 );
290
291 JPanel buttonBox = new JPanel();
292 buttonBox.setLayout(new BoxLayout(buttonBox, BoxLayout.Y_AXIS));
293 buttonBox.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
294 buttonBox.add(Box.createVerticalStrut(10));
295 buttonBox.add(deleteB);
296 buttonBox.add(Box.createVerticalStrut(10));
297 buttonBox.add(closeB);
298 buttonBox.add(Box.createVerticalGlue());
299 c.add(buttonBox, BorderLayout.EAST);
300 dialog.pack();
301 dialog.setSize(350, 200);
302 // center dialog in parent frame
303 Dimension parentSize = _owner.getFrame().getSize();
304 Dimension mySize = dialog.getSize();
305 dialog.setLocation(parentSize.width/2 - mySize.width/2,
306 parentSize.height/2 - mySize.height/2);
307 dialog.setVisible(true);
308 }
309
310 /***
311 * Return the itemname (abbreviated itemname if necessary)
312 * to be shown in the file menu open item list.
313 * A maximum of MAX_ITEM_LEN characters is used for the
314 * itemname because we do not want to make the JMenuItem
315 * entry too wide.
316 */
317 protected String getItemname(String pathname)
318 {
319 final char FILE_SEPARATOR = FILE_SEPARATOR_STR.charAt(0);
320 final int pathnameLen = pathname.length();
321
322 // if the pathame is short enough: return whole pathname
323 if (pathnameLen <= MAX_ITEM_LEN)
324 {
325 return pathname;
326 }
327
328 // if we have only one directory: return whole pathname
329 // we will not cut to MAX_ITEM_LEN here
330 if (pathname.indexOf(FILE_SEPARATOR_STR) == pathname.lastIndexOf(FILE_SEPARATOR_STR))
331 {
332 return pathname;
333 }
334 else
335 {
336 // abbreviate pathanme: Windows OS like solution
337 final int ABBREVIATED_PREFIX_LEN = 6; // e.g.: C:\..\
338 final int MAX_PATHNAME_LEN = MAX_ITEM_LEN - ABBREVIATED_PREFIX_LEN;
339 int firstFileSeparatorIndex = 0;
340 for (int i = pathnameLen - 1; i >= (pathnameLen - MAX_PATHNAME_LEN); i--)
341 {
342 if (pathname.charAt(i) == FILE_SEPARATOR)
343 {
344 firstFileSeparatorIndex = i;
345 }
346 }
347 if (firstFileSeparatorIndex > 0)
348 {
349 return pathname.substring(0, 3)
350 + ".."
351 + pathname.substring(firstFileSeparatorIndex, pathnameLen);
352 }
353 else
354 {
355 return pathname.substring(0, 3)
356 + ".."
357 + FILE_SEPARATOR_STR
358 + ".."
359 + pathname.substring(pathnameLen-MAX_PATHNAME_LEN, pathnameLen);
360 }
361 }
362 }
363
364 /***
365 * Create a JList instance with _itemnameHistory as its model.
366 */
367 private final JList createItemList()
368 {
369 ListModel model = new ListModel();
370 JList list = new JList(model);
371 list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
372 return list;
373 }
374
375
376 //////////////////////////////////////////////////////////
377 // Inner classes
378
379 /***
380 * Create a tooltip location directly over the menu item,
381 * ie, left allign the tooltip text in "overlay" technique.
382 */
383 private final class MenuItemWithFixedTooltip extends JMenuItem
384 {
385 public MenuItemWithFixedTooltip(int position, String text)
386 {
387 super(position + ": " + text);
388 }
389
390 public Point getToolTipLocation(MouseEvent e)
391 {
392 Graphics g = getGraphics();
393 FontMetrics metrics = g.getFontMetrics(g.getFont());
394 String prefix = FileHistory.this._itemnameHistory.size() <= 9 ? "8: " : "88: ";
395 int prefixWidth = metrics.stringWidth(prefix);
396 int x = JButton.TRAILING + JButton.LEADING - 1 + prefixWidth;
397 return new Point(x, 0);
398 }
399 } /* end innser class MenuItemWithFixedTooltip */
400
401 /***
402 * Listen to menu item selections.
403 */
404 private final class ItemListener implements ActionListener
405 {
406 private int _itemNbr;
407
408 ItemListener(int itemNbr)
409 {
410 _itemNbr = itemNbr;
411 }
412
413 public void actionPerformed(ActionEvent e)
414 {
415 _owner.loadFile((String) _pathnameHistory.get(_itemNbr));
416 JMenuItem item = (JMenuItem) e.getSource();
417 FileHistory.this.insertPathname(item.getToolTipText());
418 }
419 } /* end inner class ItemListener */
420
421 /***
422 * The list model for our File History dialog itemList.
423 */
424 private final class ListModel extends AbstractListModel
425 {
426 public Object getElementAt(int i)
427 {
428 return FileHistory.this._itemnameHistory.get(i);
429 }
430
431 public int getSize()
432 {
433 return FileHistory.this._itemnameHistory.size();
434 }
435 } /* end inner class ListModel */
436
437 } /* end class FileHistory */
This page was automatically generated by Maven