package me.grax.jbytemod.ui.lists; import java.awt.Font; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import me.grax.jbytemod.JByteMod; import me.grax.jbytemod.ui.JAnnotationEditor; import me.grax.jbytemod.ui.JSearch; import me.grax.jbytemod.ui.dialogue.InsnEditDialogue; import me.grax.jbytemod.ui.lists.entries.FieldEntry; import me.grax.jbytemod.ui.lists.entries.InstrEntry; import me.grax.jbytemod.ui.lists.entries.PrototypeEntry; import me.grax.jbytemod.utils.ErrorDisplay; import me.grax.jbytemod.utils.HtmlSelection; import me.grax.jbytemod.utils.list.LazyListModel; import me.lpk.util.OpUtils; public class MyCodeList extends JList<InstrEntry> { private JLabel editor; private AdressList adressList; private ErrorList errorList; private MethodNode currentMethod; private ClassNode currentClass; public MyCodeList(JByteMod jam, JLabel editor) { super(new LazyListModel<InstrEntry>()); this.editor = editor; this.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13)); this.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { InstrEntry entry = (InstrEntry) MyCodeList.this.getSelectedValue(); if (entry == null) { createPopupForEmptyList(jam); return; } MethodNode mn = entry.getMethod(); if (SwingUtilities.isRightMouseButton(e)) { if (mn != null) { AbstractInsnNode ain = entry.getInstr(); rightClickMethod(jam, mn, ain, MyCodeList.this.getSelectedValuesList()); } else { rightClickField(jam, (FieldEntry) entry, MyCodeList.this.getSelectedValuesList()); } } else if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) { if (mn != null) { try { if (InsnEditDialogue.canEdit(entry.getInstr())) { new InsnEditDialogue(mn, entry.getInstr()).open(); } } catch (Exception e1) { new ErrorDisplay(e1); } } else { FieldEntry fe = (FieldEntry) entry; try { new InsnEditDialogue(null, fe.getFn()).open(); } catch (Exception e1) { new ErrorDisplay(e1); } MyCodeList.this.loadFields(fe.getCn()); } } } }); InputMap im = getInputMap(WHEN_FOCUSED); ActionMap am = getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK), "search"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "copy"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_MASK), "duplicate"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_MASK), "insert"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), "insert"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "up"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "down"); am.put("search", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { new JSearch(MyCodeList.this).setVisible(true); } }); am.put("copy", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { copyToClipbord(); } }); am.put("duplicate", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { InstrEntry entry = getSelectedValue(); if (entry != null && entry.getMethod() != null) { duplicate(entry.getMethod(), entry.getInstr()); } } }); am.put("insert", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { InstrEntry entry = getSelectedValue(); if (entry != null && entry.getMethod() != null) { try { InsnEditDialogue.createInsertInsnDialog(entry.getMethod(), entry.getInstr(), true); OpUtils.clearLabelCache(); } catch (Exception e1) { new ErrorDisplay(e1); } } } }); am.put("delete", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { List<InstrEntry> entries = getSelectedValuesList(); for (InstrEntry entry : entries) { if (entry.getMethod() != null) { removeNode(entry.getMethod(), entry.getInstr()); } } } }); am.put("up", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { InstrEntry entry = getSelectedValue(); if (entry != null && entry.getMethod() != null) { int index = getSelectedIndex(); if (moveUp(entry.getMethod(), entry.getInstr())) { setSelectedIndex(index - 1); } } } }); am.put("down", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { InstrEntry entry = getSelectedValue(); if (entry != null && entry.getMethod() != null) { int index = getSelectedIndex(); if (moveDown(entry.getMethod(), entry.getInstr())) { setSelectedIndex(index + 1); } } } }); addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { if (JByteMod.ops.get("hints").getBoolean()) { ListModel<InstrEntry> m = getModel(); int index = locationToIndex(e.getPoint()); if (index > -1) { InstrEntry el = m.getElementAt(index); setToolTipText(el.getHint()); } } else { setToolTipText(null); } } }); this.setPrototypeCellValue(new PrototypeEntry()); this.setFixedCellWidth(-1); } protected void rightClickField(JByteMod jbm, FieldEntry fle, List<InstrEntry> selected) { ClassNode cn = fle.getCn(); JPopupMenu menu = new JPopupMenu(); if (selected.size() > 1) { JMenuItem remove = new JMenuItem(JByteMod.res.getResource("remove_all")); remove.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (InstrEntry sel : selected) { cn.fields.remove(((FieldEntry) sel).getFn()); } MyCodeList.this.loadFields(cn); } }); menu.add(remove); menu.add(copyText()); menu.show(jbm, (int) jbm.getMousePosition().getX(), (int) jbm.getMousePosition().getY()); } else { JMenuItem edit = new JMenuItem(JByteMod.res.getResource("edit")); edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { new InsnEditDialogue(null, fle.getFn()).open(); } catch (Exception e1) { new ErrorDisplay(e1); } MyCodeList.this.loadFields(cn); } }); menu.add(edit); JMenuItem remove = new JMenuItem(JByteMod.res.getResource("remove")); remove.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cn.fields.remove(fle.getFn()); MyCodeList.this.loadFields(cn); } }); menu.add(remove); JMenuItem add = new JMenuItem(JByteMod.res.getResource("insert")); add.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { FieldNode fn = new FieldNode(1, "", "", "", null); if (new InsnEditDialogue(null, fn).open()) { cn.fields.add(fn); } } catch (Exception e1) { new ErrorDisplay(e1); } MyCodeList.this.loadFields(cn); } }); menu.add(add); menu.add(copyText()); JMenuItem annotations = new JMenuItem("Edit Annotations"); annotations.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!JAnnotationEditor.isOpen("visibleAnnotations")) new JAnnotationEditor("Annotations", fle.getFn(), "visibleAnnotations").setVisible(true); } }); menu.add(annotations); JMenuItem invisAnnotations = new JMenuItem("Edit Invis Annotations"); invisAnnotations.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (!JAnnotationEditor.isOpen("invisibleAnnotations")) new JAnnotationEditor("Invis Annotations", fle.getFn(), "invisibleAnnotations").setVisible(true); } }); menu.add(invisAnnotations); menu.show(jbm, (int) jbm.getMousePosition().getX(), (int) jbm.getMousePosition().getY()); } } private JMenuItem copyText() { JMenuItem copy = new JMenuItem(JByteMod.res.getResource("copy_text")); copy.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { copyToClipbord(); JByteMod.LOGGER.log("Copied code to clipboard!"); } }); return copy; } protected void copyToClipbord() { StringBuilder sb = new StringBuilder(); boolean html = JByteMod.ops.get("copy_formatted").getBoolean(); if (html) { for (InstrEntry sel : MyCodeList.this.getSelectedValuesList()) { sb.append(sel.toString()); sb.append("<br>"); } } else { for (InstrEntry sel : MyCodeList.this.getSelectedValuesList()) { sb.append(sel.toEasyString()); sb.append("\n"); } } if (sb.length() > 0) { HtmlSelection selection = new HtmlSelection(sb.toString()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); } } protected void rightClickMethod(JByteMod jbm, MethodNode mn, AbstractInsnNode ain, List<InstrEntry> selected) { if (selected.size() > 1) { JPopupMenu menu = new JPopupMenu(); JMenuItem remove = new JMenuItem(JByteMod.res.getResource("remove_all")); remove.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (InstrEntry sel : selected) { mn.instructions.remove(sel.getInstr()); } OpUtils.clearLabelCache(); MyCodeList.this.loadInstructions(mn); } }); menu.add(remove); menu.add(copyText()); addPopupListener(menu); menu.show(jbm, (int) jbm.getMousePosition().getX(), (int) jbm.getMousePosition().getY()); } else { JPopupMenu menu = new JPopupMenu(); JMenuItem insertBefore = new JMenuItem(JByteMod.res.getResource("ins_before")); insertBefore.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { InsnEditDialogue.createInsertInsnDialog(mn, ain, false); OpUtils.clearLabelCache(); } catch (Exception e1) { new ErrorDisplay(e1); } } }); menu.add(insertBefore); JMenuItem insert = new JMenuItem(JByteMod.res.getResource("ins_after")); insert.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { InsnEditDialogue.createInsertInsnDialog(mn, ain, true); OpUtils.clearLabelCache(); } catch (Exception e1) { new ErrorDisplay(e1); } } }); insert.setAccelerator(KeyStroke.getKeyStroke('I', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); menu.add(insert); if (InsnEditDialogue.canEdit(ain)) { JMenuItem edit = new JMenuItem(JByteMod.res.getResource("edit")); edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { new InsnEditDialogue(mn, ain).open(); } catch (Exception e1) { new ErrorDisplay(e1); } } }); menu.add(edit); } if (ain instanceof JumpInsnNode) { JMenuItem edit = new JMenuItem(JByteMod.res.getResource("jump_to_label")); edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JumpInsnNode jin = (JumpInsnNode) ain; ListModel<InstrEntry> model = getModel(); for (int i = 0; i < model.getSize(); i++) { InstrEntry sel = model.getElementAt(i); if (sel.getInstr().equals(jin.label)) { setSelectedIndex(i); ensureIndexIsVisible(i); break; } } } }); menu.add(edit); } if (ain instanceof MethodInsnNode) { JMenuItem edit = new JMenuItem(JByteMod.res.getResource("go_to_dec")); edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { MethodInsnNode min = (MethodInsnNode) ain; for (ClassNode cn : jbm.getFile().getClasses().values()) { if (cn.name.equals(min.owner)) { for (MethodNode mn : cn.methods) { if (min.name.equals(mn.name) && min.desc.equals(mn.desc)) { jbm.selectMethod(cn, mn); jbm.treeSelection(cn, mn); return; } } } } } }); menu.add(edit); } if (ain instanceof FieldInsnNode) { JMenuItem edit = new JMenuItem(JByteMod.res.getResource("go_to_dec")); edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { FieldInsnNode fin = (FieldInsnNode) ain; for (ClassNode cn : jbm.getFile().getClasses().values()) { if (cn.name.equals(fin.owner)) { jbm.selectClass(cn); return; } } } }); menu.add(edit); } JMenuItem duplicate = new JMenuItem(JByteMod.res.getResource("duplicate")); duplicate.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { duplicate(mn, ain); } }); duplicate.setAccelerator(KeyStroke.getKeyStroke('D', Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); menu.add(duplicate); JMenuItem up = new JMenuItem(JByteMod.res.getResource("move_up")); up.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveUp(mn, ain); } }); up.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); menu.add(up); JMenuItem down = new JMenuItem(JByteMod.res.getResource("move_down")); down.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { moveDown(mn, ain); } }); down.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); menu.add(down); JMenuItem remove = new JMenuItem(JByteMod.res.getResource("remove")); remove.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { removeNode(mn, ain); } }); remove.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)); menu.add(copyText()); menu.add(remove); addPopupListener(menu); menu.show(jbm, (int) jbm.getMousePosition().getX(), (int) jbm.getMousePosition().getY()); } } protected void removeNode(MethodNode mn, AbstractInsnNode ain) { mn.instructions.remove(ain); OpUtils.clearLabelCache(); loadInstructions(mn); } protected boolean moveDown(MethodNode mn, AbstractInsnNode ain) { AbstractInsnNode node = ain.getNext(); if (node != null) { mn.instructions.remove(node); mn.instructions.insertBefore(ain, node); OpUtils.clearLabelCache(); loadInstructions(mn); return true; } return false; } protected boolean moveUp(MethodNode mn, AbstractInsnNode ain) { AbstractInsnNode node = ain.getPrevious(); if (node != null) { mn.instructions.remove(node); mn.instructions.insert(ain, node); OpUtils.clearLabelCache(); loadInstructions(mn); return true; } return false; } protected void duplicate(MethodNode mn, AbstractInsnNode ain) { try { if (ain instanceof LabelNode) { mn.instructions.insert(ain, new LabelNode()); OpUtils.clearLabelCache(); } else if (ain instanceof JumpInsnNode) { mn.instructions.insert(ain, new JumpInsnNode(ain.getOpcode(), ((JumpInsnNode) ain).label)); } else { mn.instructions.insert(ain, ain.clone(new HashMap<>())); } MyCodeList.this.loadInstructions(mn); } catch (Exception e1) { new ErrorDisplay(e1); } } protected void createPopupForEmptyList(JByteMod jbm) { JPopupMenu menu = new JPopupMenu(); if (currentMethod != null) { JMenuItem add = new JMenuItem(JByteMod.res.getResource("add")); add.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { InsnEditDialogue.createInsertInsnDialog(currentMethod, null, true); } catch (Exception e1) { new ErrorDisplay(e1); } } }); menu.add(add); } else if (currentClass != null) { JMenuItem add = new JMenuItem(JByteMod.res.getResource("add")); add.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { FieldNode fn = new FieldNode(1, "", "", "", null); if (new InsnEditDialogue(null, fn).open()) { currentClass.fields.add(fn); } } catch (Exception e1) { new ErrorDisplay(e1); } MyCodeList.this.loadFields(currentClass); } }); menu.add(add); } menu.show(jbm, (int) jbm.getMousePosition().getX(), (int) jbm.getMousePosition().getY()); } protected void addPopupListener(JPopupMenu menu) { menu.addPopupMenuListener(new PopupMenuListener() { public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) { MyCodeList.this.setFocusable(true); } public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) { MyCodeList.this.setFocusable(true); } public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) { MyCodeList.this.setFocusable(false); } }); } public boolean loadInstructions(MethodNode m) { this.currentMethod = m; this.currentClass = null; LazyListModel<InstrEntry> lm = new LazyListModel<InstrEntry>(); editor.setText(m.name + m.desc); ArrayList<InstrEntry> entries = new ArrayList<>(); for (AbstractInsnNode i : m.instructions) { InstrEntry entry = new InstrEntry(m, i); lm.addElement(entry); entries.add(entry); } this.setModel(lm); //update sidebar if (adressList != null) { adressList.updateAdr(); } if (errorList != null) { errorList.updateErrors(); } return true; } public void setAdressList(AdressList adressList) { this.adressList = adressList; } public boolean loadFields(ClassNode cn) { this.currentClass = cn; this.currentMethod = null; LazyListModel<InstrEntry> lm = new LazyListModel<InstrEntry>(); editor.setText(cn.name + " Fields"); ArrayList<InstrEntry> entries = new ArrayList<>(); for (FieldNode fn : cn.fields) { InstrEntry entry = new FieldEntry(cn, fn); lm.addElement(entry); entries.add(entry); } this.setModel(lm); //update sidebar if (adressList != null) { adressList.updateAdr(); } if (errorList != null) { errorList.updateErrors(); } return true; } public void setErrorList(ErrorList errorList) { this.errorList = errorList; } }