/* * AddinsMRUList.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.workbench; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.rstudio.core.client.CommandWithArg; import org.rstudio.core.client.StringUtil; import org.rstudio.core.client.command.AppCommand; import org.rstudio.core.client.command.CommandHandler; import org.rstudio.core.client.command.KeyMap; import org.rstudio.core.client.command.KeyboardShortcut; import org.rstudio.core.client.command.EditorCommandManager.EditorKeyBindings; import org.rstudio.core.client.command.KeyboardShortcut.KeySequence; import org.rstudio.core.client.command.ShortcutManager; import org.rstudio.core.client.command.KeyMap.KeyMapType; import org.rstudio.core.client.js.JsUtil; import org.rstudio.studio.client.application.events.EventBus; import org.rstudio.studio.client.workbench.addins.Addins.AddinExecutor; import org.rstudio.studio.client.workbench.addins.Addins.RAddin; import org.rstudio.studio.client.workbench.addins.Addins.RAddins; import org.rstudio.studio.client.workbench.addins.AddinsCommandManager; import org.rstudio.studio.client.workbench.addins.AddinsKeyBindingsChangedEvent; import org.rstudio.studio.client.workbench.addins.events.AddinRegistryUpdatedEvent; import org.rstudio.studio.client.workbench.commands.Commands; import org.rstudio.studio.client.workbench.events.ListChangedEvent; import org.rstudio.studio.client.workbench.events.ListChangedHandler; import org.rstudio.studio.client.workbench.events.SessionInitEvent; import org.rstudio.studio.client.workbench.events.SessionInitHandler; import org.rstudio.studio.client.workbench.model.Session; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @Singleton public class AddinsMRUList implements SessionInitHandler, AddinRegistryUpdatedEvent.Handler, AddinsKeyBindingsChangedEvent.Handler { @Inject public AddinsMRUList(Provider<WorkbenchListManager> pListManager, Provider<AddinsCommandManager> pAddinManager, Session session, EventBus events, Commands commands) { pListManager_ = pListManager; pAddinManager_ = pAddinManager; session_ = session; events_ = events; commands_ = commands; mruCommands_ = new AppCommand[] { commands_.addinsMru0(), commands_.addinsMru1(), commands_.addinsMru2(), commands_.addinsMru3(), commands_.addinsMru4(), commands_.addinsMru5(), commands_.addinsMru6(), commands_.addinsMru7(), commands_.addinsMru8(), commands_.addinsMru9(), commands_.addinsMru10(), commands_.addinsMru11(), commands_.addinsMru12(), commands_.addinsMru13(), commands_.addinsMru14() }; events_.addHandler(SessionInitEvent.TYPE, this); events_.addHandler(AddinRegistryUpdatedEvent.TYPE, this); events_.addHandler(AddinsKeyBindingsChangedEvent.TYPE, this); initCommandHandlers(); } private void initCommandHandlers() { for (int i = 0; i < mruCommands_.length; i++) addIndexedHandler(mruCommands_[i], i); } private void addIndexedHandler(AppCommand command, int index) { command.addHandler(new AddinCommandHandler(index)); } @Override public void onSessionInit(SessionInitEvent sie) { RAddins addins = session_.getSessionInfo().getAddins(); update(addins); mruList_ = pListManager_.get().getAddinsMruList(); mruList_.addListChangedHandler(new ListChangedHandler() { @Override public void onListChanged(ListChangedEvent event) { mruEntries_ = event.getList(); update(addinRegistry_); } }); } @Override public void onAddinRegistryUpdated(AddinRegistryUpdatedEvent event) { update(event.getData()); } @Override public void onAddinsKeyBindingsChanged(AddinsKeyBindingsChangedEvent event) { update(addinRegistry_); } private void update(RAddins addins) { List<RAddin> addinsList = new ArrayList<RAddin>(); for (String key : JsUtil.asIterable(addins.keys())) addinsList.add(addins.get(key)); update(addinsList); } private void update(final List<RAddin> addins) { addinRegistry_ = addins; pAddinManager_.get().loadBindings(new CommandWithArg<EditorKeyBindings>() { @Override public void execute(EditorKeyBindings bindings) { finishUpdate(addins); } }); } private void finishUpdate(List<RAddin> addinRegistry) { // The list that will eventually hold the backing set // of addins that the dummy MRU commands will dispatch to List<RAddin> addinList = new ArrayList<RAddin>(); // Map used for quick lookup of MRU addin ids Map<String, RAddin> addinMap = new HashMap<String, RAddin>(); for (RAddin addin : addinRegistry) addinMap.put(addin.getId(), addin); // Collect addins. First, collect addins in the MRU list. for (String id : mruEntries_) { if (addinList.size() >= MRU_LIST_SIZE) break; if (addinMap.containsKey(id)) addinList.add(addinMap.get(id)); } // Now, collect the rest of the addins (that haven't been added // to the backing list. for (RAddin addin : addinRegistry) { if (addinList.size() >= MRU_LIST_SIZE) break; if (!addinList.contains(addin)) addinList.add(addin); } // Sort the addins list, favoring addins that have // been recently updated. Collections.sort(addinList, new Comparator<RAddin>() { @Override public int compare(RAddin o1, RAddin o2) { int compare = 0; // Compare first on package name. compare = o1.getPackage().compareTo(o2.getPackage()); if (compare != 0) return compare; // Then compare on actual name. compare = o1.getName().compareTo(o2.getName()); if (compare != 0) return compare; return 0; } }); // Save the list (so that the dummy commands can be routed properly) addinList_ = addinList; KeyMap addinsKeyMap = ShortcutManager.INSTANCE.getKeyMap(KeyMapType.ADDIN); // Populate up to 15 commands. for (int i = 0; i < mruCommands_.length; i++) manageCommand(mruCommands_[i], addinList_, addinsKeyMap, i); } private class AddinCommandHandler implements CommandHandler { public AddinCommandHandler(int index) { index_ = index; } @Override public void onCommand(AppCommand command) { if (executor_ == null) executor_ = new AddinExecutor(); RAddin addin = addinList_.get(index_); executor_.execute(addin); } private final int index_; private AddinExecutor executor_; } private void manageCommand(AppCommand command, List<RAddin> addinsList, KeyMap keyMap, int index) { if (index >= addinsList.size()) { command.setEnabled(false); command.setVisible(false); return; } RAddin addin = addinsList.get(index); command.setEnabled(true); command.setVisible(true); String description = addin.getDescription() + " [" + addin.getId() + "]"; command.setDesc(description); String name = StringUtil.truncate(addin.getName(), 25, "..."); command.setLabel(name); List<KeySequence> keys = keyMap.getBindings(addin.getId()); if (keys != null && !keys.isEmpty()) command.setShortcut(new KeyboardShortcut(keys.get(0))); else command.setShortcut(null); } public void add(RAddin addin) { mruList_.prepend(addin.getId()); } // Private Members ---- private WorkbenchList mruList_; private ArrayList<String> mruEntries_; // NOTE: The addinRegistry_ is distinct from the addinList_; // the addinRegistry_ contains ALL commands, while the addinList_ // is just the top n sorted commands (used as a backing for the // MRU commands) private List<RAddin> addinRegistry_; private List<RAddin> addinList_; private final AppCommand[] mruCommands_; private static final int MRU_LIST_SIZE = 15; // Injected ---- private final Provider<WorkbenchListManager> pListManager_; private final Provider<AddinsCommandManager> pAddinManager_; private final Session session_; private final EventBus events_; private final Commands commands_; }