001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.openfile;
043:
044: import java.awt.Component;
045: import java.awt.Image;
046: import java.awt.MouseInfo;
047: import java.awt.Point;
048: import java.awt.event.ActionEvent;
049: import java.beans.BeanInfo;
050: import java.util.List;
051: import java.util.logging.Level;
052: import java.util.logging.Logger;
053: import javax.swing.AbstractAction;
054: import javax.swing.Action;
055: import javax.swing.ImageIcon;
056: import javax.swing.ImageIcon;
057: import javax.swing.JComponent;
058: import javax.swing.JMenu;
059: import javax.swing.JMenuItem;
060: import javax.swing.MenuElement;
061: import javax.swing.MenuSelectionManager;
062: import javax.swing.SwingUtilities;
063: import javax.swing.event.ChangeEvent;
064: import javax.swing.event.ChangeListener;
065: import javax.swing.event.PopupMenuEvent;
066: import javax.swing.event.PopupMenuListener;
067: import org.netbeans.modules.openfile.RecentFiles.HistoryItem;
068: import org.openide.awt.DynamicMenuContent;
069: import org.openide.filesystems.FileObject;
070: import org.openide.loaders.DataObject;
071: import org.openide.loaders.DataObjectNotFoundException;
072: import org.openide.util.NbBundle;
073: import org.openide.util.Utilities;
074: import org.openide.util.actions.Presenter;
075:
076: /**
077: * Action that presents list of recently closed files/documents.
078: *
079: * @author Dafe Simonek
080: */
081: public class RecentFileAction extends AbstractAction implements
082: Presenter.Menu, PopupMenuListener, ChangeListener {
083:
084: /** property of menu items where we store fileobject to open */
085: private static final String FO_PROP = "RecentFileAction.Recent_FO";
086:
087: /** number of maximum shown items in submenu */
088: private static final int MAX_COUNT = 15;
089:
090: private JMenu menu;
091:
092: public RecentFileAction() {
093: super (NbBundle.getMessage(RecentFileAction.class,
094: "LBL_RecentFileAction_Name")); // NOI18N
095: }
096:
097: /********* Presenter.Menu impl **********/
098:
099: public JMenuItem getMenuPresenter() {
100: if (menu == null) {
101: menu = new UpdatingMenu(this );
102: menu.setMnemonic(NbBundle
103: .getMessage(RecentFileAction.class,
104: "MNE_RecentFileAction_Name").charAt(0));
105: // #115277 - workaround, PopupMenuListener don't work on Mac
106: if (!Utilities.isMac()) {
107: menu.getPopupMenu().addPopupMenuListener(this );
108: } else {
109: menu.addChangeListener(this );
110: }
111: }
112: return menu;
113: }
114:
115: /******* PopupMenuListener impl *******/
116:
117: /* Fills submenu when popup is about to be displayed.
118: * Note that argument may be null on Mac due to #115277 fix
119: */
120: public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) {
121: fillSubMenu();
122: }
123:
124: /* Clears submenu when popup is about to be hidden.
125: * Note that argument may be null on Mac due to #115277 fix
126: */
127: public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) {
128: menu.removeAll();
129: }
130:
131: public void popupMenuCanceled(PopupMenuEvent arg0) {
132: }
133:
134: /******** ChangeListener impl *********/
135:
136: /** Delegates to popupMenuListener based on menu current selection status */
137: public void stateChanged(ChangeEvent e) {
138: if (menu.isSelected()) {
139: popupMenuWillBecomeVisible(null);
140: } else {
141: popupMenuWillBecomeInvisible(null);
142: }
143: }
144:
145: /** Fills submenu with recently closed files got from RecentFiles support */
146: private void fillSubMenu() {
147: List<RecentFiles.HistoryItem> files = RecentFiles
148: .getRecentFiles();
149:
150: int counter = 0;
151: for (HistoryItem hItem : files) {
152: // obtain file object
153: // note we need not check for null or validity, as it is ensured
154: // by RecentFiles.getRecentFiles()
155: FileObject fo = RecentFiles.convertURL2File(hItem.getURL());
156: // allow only up to max items
157: if (++counter > MAX_COUNT) {
158: break;
159: }
160: // obtain icon for fileobject
161: Image icon = null;
162: try {
163: DataObject dObj = DataObject.find(fo);
164: icon = dObj.getNodeDelegate().getIcon(
165: BeanInfo.ICON_COLOR_16x16);
166: } catch (DataObjectNotFoundException ex) {
167: // should not happen, log and skip to next
168: Logger.getLogger(RecentFiles.class.getName()).log(
169: Level.INFO, ex.getMessage(), ex);
170: continue;
171: }
172: // create and configure menu item
173: JMenuItem jmi = null;
174: if (icon != null) {
175: jmi = new JMenuItem(fo.getNameExt(),
176: new ImageIcon(icon));
177: } else {
178: jmi = new JMenuItem(fo.getNameExt());
179: }
180: jmi.putClientProperty(FO_PROP, fo);
181: jmi.addActionListener(this );
182: menu.add(jmi);
183: }
184:
185: ensureSelected();
186: }
187:
188: /** Workaround for JDK bug 6663119, it ensures that first item in submenu
189: * is correctly selected during keyboard navigation.
190: */
191: private void ensureSelected() {
192: if (menu.getMenuComponentCount() <= 0) {
193: return;
194: }
195:
196: Component first = menu.getMenuComponent(0);
197: if (!(first instanceof JMenuItem)) {
198: return;
199: }
200:
201: Point loc = MouseInfo.getPointerInfo().getLocation();
202: SwingUtilities.convertPointFromScreen(loc, menu);
203: MenuElement[] selPath = MenuSelectionManager.defaultManager()
204: .getSelectedPath();
205:
206: // apply workaround only when mouse is not hovering over menu
207: // (which signalizes mouse driven menu traversing) and only
208: // when selected menu path contains expected value - submenu itself
209: if (!menu.contains(loc) && selPath.length > 0
210: && menu.getPopupMenu() == selPath[selPath.length - 1]) {
211: // select first item in submenu through MenuSelectionManager
212: MenuElement[] newPath = new MenuElement[selPath.length + 1];
213: System.arraycopy(selPath, 0, newPath, 0, selPath.length);
214: JMenuItem firstItem = (JMenuItem) first;
215: newPath[selPath.length] = firstItem;
216: MenuSelectionManager.defaultManager().setSelectedPath(
217: newPath);
218: }
219: }
220:
221: /** Opens recently closed file, using OpenFile support.
222: *
223: * Note that method works as action handler for individual submenu items
224: * created in fillSubMenu, not for whole RecentFileAction.
225: */
226: public void actionPerformed(ActionEvent evt) {
227: JMenuItem source = (JMenuItem) evt.getSource();
228: FileObject fo = (FileObject) source.getClientProperty(FO_PROP);
229: if (fo != null) {
230: OpenFile.open(fo, -1);
231: }
232: }
233:
234: /** Menu that checks its enabled state just before is populated */
235: private class UpdatingMenu extends JMenu implements
236: DynamicMenuContent {
237:
238: private final JComponent[] content = new JComponent[] { this };
239:
240: public UpdatingMenu(Action action) {
241: super (action);
242: }
243:
244: public JComponent[] getMenuPresenters() {
245: setEnabled(!RecentFiles.getRecentFiles().isEmpty());
246: return content;
247: }
248:
249: public JComponent[] synchMenuPresenters(JComponent[] items) {
250: return getMenuPresenters();
251: }
252: }
253:
254: }
|