Introduction to the Context Menu API
The goal of this quickstart is to introduce you to the the context menu api, so you will be able to add context menu extensions in your IGB Apps.
What is a context menu?
Wikipedia
"A context menu (also called contextual, shortcut, and popup or pop-up menu) is a menu in a graphical user interface (GUI) that appears upon user interaction, such as a right-click mouse operation. A context menu offers a limited set of choices that are available in the current state, or context, of the operating system or application. Usually the available choices are actions related to the selected object."
Example Context Menu on OS X 10.9
IGB Context Menu
Context Menu API Overview
Interface
package org.lorainelab.igb.context.menu; import java.util.List; import java.util.Optional; import org.lorainelab.igb.context.menu.model.AnnotationContextEvent; import org.lorainelab.igb.context.menu.model.ContextMenuItem; /** * * @author dcnorris */ public interface AnnotationContextMenuProvider { public Optional<List<ContextMenuItem>> buildMenuItem(AnnotationContextEvent event); }
Data Model
AnnotationContextEvent
package org.lorainelab.igb.context.menu.model; import com.affymetrix.genometry.symmetry.impl.SeqSymmetry; import java.util.List; /** * * @author dcnorris */ public class AnnotationContextEvent { private final List<SeqSymmetry> selectedItems; public AnnotationContextEvent(List<SeqSymmetry> selectedItems) { this.selectedItems = selectedItems; } public List<SeqSymmetry> getSelectedItems() { return selectedItems; } }
ContextMenuItem
package org.lorainelab.igb.context.menu.model; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.lorainelab.igb.context.menu.MenuSection; /** * * @author dcnorris */ public class ContextMenuItem { private final String menuLabel; private MenuIcon menuIcon; private Function<Void, Void> action; private Set<ContextMenuItem> subMenuItems; private int weight = 0; private MenuSection menuSection = MenuSection.APP; public ContextMenuItem(String menuLabel, Set<ContextMenuItem> subMenuItems) { checkNotNull(menuLabel); checkNotNull(subMenuItems); checkState(!Strings.isNullOrEmpty(menuLabel)); checkState(!subMenuItems.isEmpty()); this.menuLabel = menuLabel; this.subMenuItems = ImmutableSet.copyOf(subMenuItems); } public ContextMenuItem(String menuLabel, Function<Void, Void> action) { checkNotNull(menuLabel); checkState(!Strings.isNullOrEmpty(menuLabel)); checkNotNull(action); this.menuLabel = menuLabel; this.action = action; subMenuItems = ImmutableSet.of(); } public String getMenuLabel() { return menuLabel; } public Function<Void, Void> getAction() { return action; } public Set<ContextMenuItem> getSubMenuItems() { return subMenuItems; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public void setMenuIcon(MenuIcon menuIcon) { this.menuIcon = menuIcon; } public Optional<MenuIcon> getMenuIcon() { return Optional.ofNullable(menuIcon); } public void setMenuSection(MenuSection menuSection) { this.menuSection = menuSection; } public MenuSection getMenuSection() { return menuSection; } }
MenuIcon
package org.lorainelab.igb.context.menu.model; import com.google.common.io.BaseEncoding; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author dcnorris */ public class MenuIcon { private String encodedImage; private static final Logger logger = LoggerFactory.getLogger(MenuIcon.class); public MenuIcon(InputStream resourceAsStream) { try { encodedImage = BaseEncoding.base64().encode(IOUtils.toByteArray(resourceAsStream)); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } public byte[] getEncodedImage() { return BaseEncoding.base64().decode(encodedImage); } }
MenuSection
package org.lorainelab.igb.context.menu; /** * * @author dcnorris */ public enum MenuSection { INFORMATION, SEQUENCE, APP, UI_ACTION; }
Context Menu Sections
Context Menu Quickstart Code
ContextMenuExtension
package org.lorainelab.igb.context.menu.api.quickstart; import aQute.bnd.annotation.component.Component; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.Optional; import org.lorainelab.igb.context.menu.AnnotationContextMenuProvider; import org.lorainelab.igb.context.menu.MenuSection; import org.lorainelab.igb.context.menu.model.AnnotationContextEvent; import org.lorainelab.igb.context.menu.model.ContextMenuItem; import org.lorainelab.igb.context.menu.model.MenuIcon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author dcnorris */ @Component(immediate = true) public class ContextMenuExtension implements AnnotationContextMenuProvider { private static final Logger logger = LoggerFactory.getLogger(ContextMenuExtension.class); private static final String ICONPATH = "terminal.png"; @Override public Optional<List<ContextMenuItem>> buildMenuItem(AnnotationContextEvent event) { ContextMenuItem contextMenuItem = new ContextMenuItem("Log Selection ids", (Void t) -> { event.getSelectedItems().stream().map(selectedSym -> selectedSym.getID()).forEach(logger::info); return t; }); try (InputStream resourceAsStream = ContextMenuExtension.class.getClassLoader().getResourceAsStream(ICONPATH)) { contextMenuItem.setMenuIcon(new MenuIcon(resourceAsStream)); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } contextMenuItem.setMenuSection(MenuSection.INFORMATION); return Optional.ofNullable(Arrays.asList(contextMenuItem)); } }
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.affymetrix</groupId> <artifactId>igb-project</artifactId> <version>9.0.0</version> <relativePath>../../pom.xml</relativePath> </parent> <groupId>org.lorainelab.igb</groupId> <artifactId>context-menu-api</artifactId> <packaging>bundle</packaging> <name>Context Menu API</name> <dependencies> <dependency> <groupId>biz.aQute.bnd</groupId> <artifactId>bndlib</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.affymetrix</groupId> <artifactId>genometry</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.affymetrix</groupId> <artifactId>igb-services</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <scope>provided</scope> </dependency> <!--Start of logging dependencies--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <scope>provided</scope> </dependency> <!--End of logging dependencies--> <dependency> <groupId>com.affymetrix</groupId> <artifactId>affymetrix-common</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName> <Import-Package>*</Import-Package> <Export-Package> ${project.groupId}.context.menu, ${project.groupId}.context.menu.model </Export-Package> <Service-Component>*</Service-Component> </instructions> </configuration> </plugin> </plugins> </build> </project>
(Optional) Add README.md
Add a README.md markdown file to the root of the project for a customized APP manager display of your new app.
Example
# Context Menu API Quickstart A small example context menu extension that logs the ids of all selected SeqSymmetry objects ```java package org.lorainelab.igb.context.menu.api.quickstart; import aQute.bnd.annotation.component.Component; import java.io.InputStream; import java.util.Optional; import org.lorainelab.igb.context.menu.AnnotationContextMenuProvider; import org.lorainelab.igb.context.menu.MenuSection; import org.lorainelab.igb.context.menu.model.AnnotationContextEvent; import org.lorainelab.igb.context.menu.model.ContextMenuItem; import org.lorainelab.igb.context.menu.model.MenuIcon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author dcnorris */ @Component(immediate = true) public class ContextMenuExtension implements AnnotationContextMenuProvider { private static final Logger logger = LoggerFactory.getLogger(ContextMenuExtension.class); private static final String ICONPATH = "terminal.png"; @Override public Optional<ContextMenuItem> buildMenuItem(AnnotationContextEvent event) { ContextMenuItem contextMenuItem = new ContextMenuItem("Log Selection ids", (Void t) -> { event.getSelectedItems().stream().map(selectedSym -> selectedSym.getID()).forEach(logger::info); return t; }); try (InputStream resourceAsStream = ContextMenuExtension.class.getClassLoader().getResourceAsStream(ICONPATH)) { contextMenuItem.setMenuIcon(new MenuIcon(resourceAsStream)); } catch (Exception ex) { logger.error(ex.getMessage(), ex); } return Optional.ofNullable(contextMenuItem); } @Override public MenuSection getMenuSection() { return MenuSection.INFORMATION; } } ```
Building the Example
- Navigate to the root of the project
- use maven to build the project
mvn clean install
- The build process will result in a new folder being added into your project directory (i.e. the target folder)
- Add the target directory as a new IGB App repository
- IGB App Manger Tab -> Launch App Manager -> Manage Repositories -> Add...
- Select and add the target directory
- Use the IGB App Manager to Install the new plugin
- IGB App Manger Tab -> Launch App Manager -> Manage Repositories -> Add...
Results