/*
* 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_;
}