Skip to content

Commit

Permalink
Closes java-decompiler#43, adds "Copy Qualified Name" contextual menu
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanue1 committed Jun 26, 2015
1 parent d71bed5 commit e1a93aa
Show file tree
Hide file tree
Showing 41 changed files with 550 additions and 278 deletions.
2 changes: 2 additions & 0 deletions api/src/main/java/jd/gui/api/API.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public interface API {

public <T extends JComponent & UriGettable> void addPanel(String title, Icon icon, String tip, T component);

public Collection<Action> getContextualActions(Container.Entry entry, String fragment);

public UriLoader getUriLoader(URI uri);

public FileLoader getFileLoader(File file);
Expand Down
4 changes: 4 additions & 0 deletions api/src/main/java/jd/gui/api/model/Type.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public interface Field {

public String getDescriptor();

public String getDisplayName();

public Icon getIcon();
}

Expand All @@ -59,6 +61,8 @@ public interface Method {

public String getDescriptor();

public String getDisplayName();

public Icon getIcon();
}
}
26 changes: 26 additions & 0 deletions api/src/main/java/jd/gui/spi/ContextualActionsFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2008-2015 Emmanuel Dupuy
* This program is made available under the terms of the GPLv3 License.
*/

package jd.gui.spi;

import jd.gui.api.API;
import jd.gui.api.model.Container;

import javax.swing.*;
import java.util.Collection;

public interface ContextualActionsFactory {

public static final String GROUP_NAME = "GroupNameKey";

/**
* Build a collection of actions for 'entry' and 'fragment', grouped by GROUP_NAME and sorted by NAME. Null values
* are added for separators.
*
* @param fragment @see jd.gui.api.feature.UriOpenable
* @return a collection of actions
*/
public Collection<Action> make(API api, Container.Entry entry, String fragment);
}
3 changes: 2 additions & 1 deletion api/src/main/java/jd/gui/spi/TreeNodeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import javax.swing.tree.DefaultMutableTreeNode;

import jd.gui.api.API;
import jd.gui.api.feature.ContainerEntryGettable;
import jd.gui.api.feature.UriGettable;
import jd.gui.api.model.Container;

Expand All @@ -18,5 +19,5 @@ public interface TreeNodeFactory {

public Pattern getPathPattern();

public <T extends DefaultMutableTreeNode & UriGettable> T make(API api, Container.Entry entry);
public <T extends DefaultMutableTreeNode & ContainerEntryGettable & UriGettable> T make(API api, Container.Entry entry);
}
7 changes: 7 additions & 0 deletions app/src/main/groovy/jd/gui/controller/MainController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import jd.gui.service.fileloader.FileLoaderService
import jd.gui.service.indexer.IndexerService
import jd.gui.service.mainpanel.PanelFactoryService
import jd.gui.service.pastehandler.PasteHandlerService
import jd.gui.service.actions.ContextualActionsFactoryService
import jd.gui.service.preferencespanel.PreferencesPanelService
import jd.gui.service.sourcesaver.SourceSaverService
import jd.gui.service.treenode.TreeNodeFactoryService
Expand All @@ -42,6 +43,7 @@ import jd.gui.spi.TypeFactory
import jd.gui.spi.UriLoader
import jd.gui.util.net.UriUtil

import javax.swing.Action
import javax.swing.Icon
import javax.swing.JComponent
import javax.swing.JFileChooser
Expand Down Expand Up @@ -92,6 +94,7 @@ class MainController implements API {
mainView = new MainView(
swing,
configuration,
this,
history,
{ panelClosed() }, // panelClosedClosure
{ page -> onCurrentPageChanged(page) }, // currentPageChangedClosure
Expand Down Expand Up @@ -161,6 +164,7 @@ class MainController implements API {
// Background controller creation
selectLocationController = new SelectLocationController(swing, configuration, MainController.this)
// Background service initialization
ContextualActionsFactoryService.instance
ContainerFactoryService.instance
FileLoaderService.instance
IndexerService.instance
Expand Down Expand Up @@ -515,6 +519,9 @@ class MainController implements API {
checkIndexesChange(currentPage)
}

@CompileStatic
Collection<Action> getContextualActions(Container.Entry entry, String fragment) { ContextualActionsFactoryService.instance.get(this, entry, fragment) }

@CompileStatic
FileLoader getFileLoader(File file) { FileLoaderService.instance.get(this, file) }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2008-2015 Emmanuel Dupuy
* This program is made available under the terms of the GPLv3 License.
*/

package jd.gui.service.actions

import groovy.transform.CompileStatic
import jd.gui.api.API
import jd.gui.api.model.Container
import jd.gui.spi.ContextualActionsFactory

import javax.swing.Action

@Singleton(lazy = true)
class ContextualActionsFactoryService {
static final ActionNameComparator COMPARATOR = new ActionNameComparator()

final List<ContextualActionsFactory> providers = ServiceLoader.load(ContextualActionsFactory).toList()

@CompileStatic
Collection<Action> get(API api, Container.Entry entry, String fragment) {
Map<String, ArrayList<Action>> mapActions = [:].withDefault { [] }

for (def provider : providers) {
def actions = provider.make(api, entry, fragment)

for (def action : actions) {
mapActions.get(action.getValue(ContextualActionsFactory.GROUP_NAME)).add(action)
}
}

if (mapActions) {
def result = new ArrayList<Action>()

// Sort by group names
for (def groupName : mapActions.keySet().sort()) {
if (! result.isEmpty()) {
// Add 'null' to mark a separator
result.add(null)
}

// Sort by names
def actions = mapActions.get(groupName)
Collections.sort(actions, COMPARATOR)
result.addAll(actions)
}

return result
} else {
return Collections.emptyList()
}
}

static class ActionNameComparator implements Comparator<Action> {

int compare(Action a1, Action a2) {
String n1 = a1.getValue(Action.NAME) ?: ''
String n2 = a2.getValue(Action.NAME) ?: ''
return n1.compareTo(n2)
}

boolean equals(Object other) { this == other }
}
}
2 changes: 1 addition & 1 deletion app/src/main/groovy/jd/gui/view/MainDescription.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ frame(
iconButton(action:backwardAction, text:null, icon:imageIcon(resource:'/images/backward_nav.png'))
iconButton(action:forwardAction, text:null, icon:imageIcon(resource:'/images/forward_nav.png'))
}
mainTabbedPanel(id:'mainTabbedPanel', constraints:CENTER)
mainTabbedPanel(id:'mainTabbedPanel', constraints:CENTER, api:api)
hbox(id:'findPanel', constraints:PAGE_END, border:emptyBorder(2), visible:false) {
label(text:'Find: ')
comboBox(id:'findComboBox', editable: true)
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/groovy/jd/gui/view/MainView.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package jd.gui.view

import groovy.swing.SwingBuilder
import jd.gui.Constants
import jd.gui.api.API
import jd.gui.api.feature.ContentSearchable
import jd.gui.api.feature.ContentSelectable
import jd.gui.api.feature.LineNumberNavigable
Expand Down Expand Up @@ -50,7 +51,7 @@ class MainView implements UriOpenable, PreferencesChangeListener {
Color findErrorBackgroundColor

MainView(
SwingBuilder swing, Configuration configuration, History history,
SwingBuilder swing, Configuration configuration, API api, History history,
Closure panelClosedClosure,
Closure currentPageChangedClosure,
Closure openFilesClosure,
Expand All @@ -63,6 +64,7 @@ class MainView implements UriOpenable, PreferencesChangeListener {
// Setup
registerBeanFactory('iconButton', IconButton.class)
registerBeanFactory('mainTabbedPanel', MainTabbedPanel.class)
registerExplicitProperty('api', { api }, {}) // Used to init 'mainTabbedPanel.api'
// Load GUI description
build(MainDescription)
// Add listeners
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/groovy/jd/gui/view/component/panel/TabbedPanel.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package jd.gui.view.component.panel

import jd.gui.api.API
import jd.gui.api.feature.PreferencesChangeListener
import jd.gui.api.feature.UriGettable
import jd.gui.service.platform.PlatformService
Expand Down Expand Up @@ -37,6 +38,7 @@ class TabbedPanel extends JPanel implements PreferencesChangeListener {

static final String TAB_LAYOUT = 'UITabsPreferencesProvider.singleLineTabs'

API api
JTabbedPane tabbedPane
Map<String, String> preferences

Expand Down Expand Up @@ -158,6 +160,7 @@ class TabbedPanel extends JPanel implements PreferencesChangeListener {

class PopupTabMenu extends JPopupMenu {
PopupTabMenu(Component component) {
// Add default popup menu entries
def menuItem = new JMenuItem('Close', null)
menuItem.addActionListener(new ActionListener() {
void actionPerformed(ActionEvent e) { removeComponent(component) }
Expand All @@ -175,6 +178,21 @@ class TabbedPanel extends JPanel implements PreferencesChangeListener {
void actionPerformed(ActionEvent e) { removeAllComponents() }
})
add(menuItem)

// Add SPI popup menu entries
def actions = api.getContextualActions(component.entry, null)

if (actions) {
addSeparator()

for (def action : actions) {
if (action) {
add(action)
} else {
addSeparator()
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import javax.swing.event.TreeSelectionEvent
import javax.swing.event.TreeSelectionListener
import javax.swing.tree.DefaultMutableTreeNode
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.util.List

class TreeTabbedPanel extends JPanel implements UriGettable, UriOpenable, PageChangeable, PageClosable, PreferencesChangeListener {
Expand Down Expand Up @@ -72,8 +74,33 @@ class TreeTabbedPanel extends JPanel implements UriGettable, UriOpenable, PageCh
}
void treeCollapsed(TreeExpansionEvent e) {}
})
tree.addMouseListener(new MouseAdapter() {
void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
def path = tree.getClosestPathForLocation(e.x, e.y)

if (path) {
def node = path.lastPathComponent
def actions = api.getContextualActions(node.entry, node.uri.fragment)

if (actions) {
def popup = new JPopupMenu()
for (def action : actions) {
if (action) {
popup.add(action)
} else {
popup.addSeparator()
}
}
popup.show(e.component, e.x, e.y)
}
}
}
}
})

tabbedPanel = new TabbedPanel()
tabbedPanel.api = api
tabbedPanel.setMinimumSize([150, 10] as Dimension)
tabbedPanel.tabbedPane.addChangeListener(new ChangeListener() {
void stateChanged(ChangeEvent e) { pageChanged() }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2008-2015 Emmanuel Dupuy
* This program is made available under the terms of the GPLv3 License.
*/

package jd.gui.service.actions

import com.sun.media.sound.InvalidFormatException
import jd.gui.api.API
import jd.gui.api.model.Container
import jd.gui.spi.ContextualActionsFactory

import javax.swing.AbstractAction
import javax.swing.Action
import javax.swing.ImageIcon
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
import java.awt.event.ActionEvent

class CopyQualifiedNameContextualActionsFactory implements ContextualActionsFactory {

Collection<Action> make(API api, Container.Entry entry, String fragment) {
return Collections.singletonList(new CopyQualifiedNameAction(api, entry, fragment))
}

static class CopyQualifiedNameAction extends AbstractAction {
static final ImageIcon ICON = new ImageIcon(CopyQualifiedNameAction.class.classLoader.getResource('images/cpyqual_menu.png'))

protected API api
protected Container.Entry entry
protected String fragment

CopyQualifiedNameAction(API api, Container.Entry entry, String fragment) {
this.api = api
this.entry = entry
this.fragment = fragment

putValue(GROUP_NAME, 'Edit > CutCopyPaste')
putValue(NAME, 'Copy Qualified Name')
putValue(SMALL_ICON, ICON)
}

void actionPerformed(ActionEvent e) {
def type = api.getTypeFactory(entry)?.make(api, entry, fragment)

if (type) {
def sb = new StringBuffer(type.displayPackageName)
int dashIndex = fragment.indexOf('-')

if (sb.length() > 0) {
sb.append('.')
}

sb.append(type.displayTypeName)

if (dashIndex != -1) {
int lastDashIndex = fragment.lastIndexOf('-')

if (dashIndex == lastDashIndex) {
// See jd.gui.api.feature.UriOpenable
throw new InvalidFormatException('fragment: ' + fragment)
} else {
def name = fragment.substring(dashIndex+1, lastDashIndex)
def descriptor = fragment.substring(lastDashIndex+1)

if (descriptor.startsWith('(')) {
for (def method : type.methods) {
if (method.name.equals(name) && method.descriptor.equals(descriptor)) {
sb.append('.').append(method.displayName)
break
}
}
} else {
for (def field : type.fields) {
if (field.name.equals(name) && field.descriptor.equals(descriptor)) {
sb.append('.').append(field.displayName)
break
}
}
}
}
}

Toolkit.defaultToolkit.systemClipboard.setContents(new StringSelection(sb.toString()), null)
} else {
// Copy path of entry
def path = new File(entry.uri).absolutePath
Toolkit.defaultToolkit.systemClipboard.setContents(new StringSelection(path), null)
}
}
}
}
Loading

0 comments on commit e1a93aa

Please sign in to comment.