package us.deathmarine.luyten; import java.awt.Component; import java.awt.Cursor; import java.awt.Font; import java.awt.Panel; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.io.StringWriter; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JScrollBar; import javax.swing.JViewport; import javax.swing.ScrollPaneConstants; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.HyperlinkEvent; import org.fife.ui.rsyntaxtextarea.LinkGenerator; import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.Theme; import org.fife.ui.rtextarea.RTextScrollPane; import com.strobel.assembler.metadata.MetadataSystem; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.decompiler.DecompilationOptions; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.PlainTextOutput; import com.strobel.decompiler.languages.Languages; public class OpenFile implements SyntaxConstants { public static final HashSet<String> WELL_KNOWN_TEXT_FILE_EXTENSIONS = new HashSet<>( Arrays.asList(".java", ".xml", ".rss", ".project", ".classpath", ".h", ".c", ".cpp", ".yaml", ".yml", ".ini", ".sql", ".js", ".php", ".php5", ".phtml", ".html", ".htm", ".xhtm", ".xhtml", ".lua", ".bat", ".pl", ".sh", ".css", ".json", ".txt", ".rb", ".make", ".mak", ".py", ".properties", ".prop")); // navigation links private TreeMap<Selection, String> selectionToUniqueStrTreeMap = new TreeMap<>(); private Map<String, Boolean> isNavigableCache = new ConcurrentHashMap<>(); private Map<String, String> readableLinksCache = new ConcurrentHashMap<>(); private volatile boolean isContentValid = false; private volatile boolean isNavigationLinksValid = false; private volatile boolean isWaitForLinksCursor = false; private volatile Double lastScrollPercent = null; private LinkProvider linkProvider; private String initialNavigationLink; private boolean isFirstTimeRun = true; MainWindow mainWindow; RTextScrollPane scrollPane; Panel image_pane; RSyntaxTextArea textArea; String name; String path; private ConfigSaver configSaver; private LuytenPreferences luytenPrefs; // decompiler and type references (not needed for text files) private MetadataSystem metadataSystem; private DecompilerSettings settings; private DecompilationOptions decompilationOptions; private TypeDefinition type; public OpenFile(String name, String path, Theme theme, final MainWindow mainWindow) { this.name = name; this.path = path; this.mainWindow = mainWindow; configSaver = ConfigSaver.getLoadedInstance(); luytenPrefs = configSaver.getLuytenPreferences(); textArea = new RSyntaxTextArea(25, 70); textArea.setCaretPosition(0); textArea.requestFocusInWindow(); textArea.setMarkOccurrences(true); textArea.setClearWhitespaceLinesEnabled(false); textArea.setEditable(false); textArea.setAntiAliasingEnabled(true); textArea.setCodeFoldingEnabled(true); if (name.toLowerCase().endsWith(".class") || name.toLowerCase().endsWith(".java")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_JAVA); else if (name.toLowerCase().endsWith(".xml") || name.toLowerCase().endsWith(".rss") || name.toLowerCase().endsWith(".project") || name.toLowerCase().endsWith(".classpath")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_XML); else if (name.toLowerCase().endsWith(".h") || name.toLowerCase().endsWith(".c")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_C); else if (name.toLowerCase().endsWith(".cpp")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_CPLUSPLUS); else if (name.toLowerCase().endsWith(".sql")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_SQL); else if (name.toLowerCase().endsWith(".js")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_JAVASCRIPT); else if (name.toLowerCase().endsWith(".php") || name.toLowerCase().endsWith(".php5") || name.toLowerCase().endsWith(".phtml")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_PHP); else if (name.toLowerCase().endsWith(".html") || name.toLowerCase().endsWith(".htm") || name.toLowerCase().endsWith(".xhtm") || name.toLowerCase().endsWith(".xhtml")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_HTML); else if (name.toLowerCase().endsWith(".js")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_JAVASCRIPT); else if (name.toLowerCase().endsWith(".lua")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_LUA); else if (name.toLowerCase().endsWith(".bat")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_WINDOWS_BATCH); else if (name.toLowerCase().endsWith(".pl")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_PERL); else if (name.toLowerCase().endsWith(".sh")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_UNIX_SHELL); else if (name.toLowerCase().endsWith(".css")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_CSS); else if (name.toLowerCase().endsWith(".json")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_JSON); else if (name.toLowerCase().endsWith(".ini")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_INI); else if (name.toLowerCase().endsWith(".yaml") || name.toLowerCase().endsWith(".yml")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_YAML); else if (name.toLowerCase().endsWith(".rb")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_RUBY); else if (name.toLowerCase().endsWith(".make") || name.toLowerCase().endsWith(".mak")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_MAKEFILE); else if (name.toLowerCase().endsWith(".py")) textArea.setSyntaxEditingStyle(SYNTAX_STYLE_PYTHON); else textArea.setSyntaxEditingStyle(SYNTAX_STYLE_NONE); scrollPane = new RTextScrollPane(textArea, true); scrollPane.setIconRowHeaderEnabled(true); textArea.setText(""); // Edit RTextArea's PopupMenu JPopupMenu pop = textArea.getPopupMenu(); pop.addSeparator(); JMenuItem item = new JMenuItem("Font"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JFontChooser fontChooser = new JFontChooser(); fontChooser.setSelectedFont(textArea.getFont()); fontChooser.setSelectedFontSize(textArea.getFont().getSize()); int result = fontChooser.showDialog(mainWindow); if (result == JFontChooser.OK_OPTION) { textArea.setFont(fontChooser.getSelectedFont()); luytenPrefs.setFont_size(fontChooser.getSelectedFontSize()); } } }); pop.add(item); textArea.setPopupMenu(pop); theme.apply(textArea); textArea.setFont(new Font(textArea.getFont().getName(), textArea.getFont().getStyle(), luytenPrefs.getFont_size())); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); final JScrollBar verticalScrollbar = scrollPane.getVerticalScrollBar(); if (verticalScrollbar != null) { verticalScrollbar.addAdjustmentListener(new AdjustmentListener() { @Override public void adjustmentValueChanged(AdjustmentEvent e) { String content = textArea.getText(); if (content == null || content.length() == 0) return; int scrollValue = verticalScrollbar.getValue() - verticalScrollbar.getMinimum(); int scrollMax = verticalScrollbar.getMaximum() - verticalScrollbar.getMinimum(); if (scrollMax < 1 || scrollValue < 0 || scrollValue > scrollMax) return; lastScrollPercent = (((double) scrollValue) / ((double) scrollMax)); } }); } textArea.setHyperlinksEnabled(true); textArea.setLinkScanningMask(Keymap.ctrlDownModifier()); textArea.setLinkGenerator(new LinkGenerator() { @Override public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, final int offs) { final String uniqueStr = getUniqueStrForOffset(offs); final Integer selectionFrom = getSelectionFromForOffset(offs); if (uniqueStr != null && selectionFrom != null) { return new LinkGeneratorResult() { @Override public HyperlinkEvent execute() { if (isNavigationLinksValid) onNavigationClicked(uniqueStr); return null; } @Override public int getSourceOffset() { if (isNavigationLinksValid) return selectionFrom; return offs; } }; } return null; } }); /* * Add Ctrl+Wheel Zoom for Text Size Removes all standard listeners and * writes new listeners for wheelscroll movement. */ for (MouseWheelListener listeners : scrollPane.getMouseWheelListeners()) { scrollPane.removeMouseWheelListener(listeners); } ; scrollPane.addMouseWheelListener(new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { if (e.getWheelRotation() == 0) { // Nothing to do here. This happens when scroll event is delivered from a touchbar // or MagicMouse. There's getPreciseWheelRotation, however it looks like there's no // trivial and consistent way to use that // See https://github.com/JetBrains/intellij-community/blob/21c99af7c78fc82aefc4d05646389f4991b08b38/bin/idea.properties#L133-L156 return; } if ((e.getModifiersEx() & Keymap.ctrlDownModifier()) != 0) { Font font = textArea.getFont(); int size = font.getSize(); if (e.getWheelRotation() > 0) { textArea.setFont(new Font(font.getName(), font.getStyle(), --size >= 8 ? --size : 8)); } else { textArea.setFont(new Font(font.getName(), font.getStyle(), ++size)); } luytenPrefs.setFont_size(size); } else { if (scrollPane.isWheelScrollingEnabled() && e.getWheelRotation() != 0) { JScrollBar toScroll = scrollPane.getVerticalScrollBar(); int direction = e.getWheelRotation() < 0 ? -1 : 1; int orientation = SwingConstants.VERTICAL; if (toScroll == null || !toScroll.isVisible()) { toScroll = scrollPane.getHorizontalScrollBar(); if (toScroll == null || !toScroll.isVisible()) { return; } orientation = SwingConstants.HORIZONTAL; } e.consume(); if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { JViewport vp = scrollPane.getViewport(); if (vp == null) { return; } Component comp = vp.getView(); int units = Math.abs(e.getUnitsToScroll()); boolean limitScroll = Math.abs(e.getWheelRotation()) == 1; Object fastWheelScroll = toScroll.getClientProperty("JScrollBar.fastWheelScrolling"); if (Boolean.TRUE == fastWheelScroll && comp instanceof Scrollable) { Scrollable scrollComp = (Scrollable) comp; Rectangle viewRect = vp.getViewRect(); int startingX = viewRect.x; boolean leftToRight = comp.getComponentOrientation().isLeftToRight(); int scrollMin = toScroll.getMinimum(); int scrollMax = toScroll.getMaximum() - toScroll.getModel().getExtent(); if (limitScroll) { int blockIncr = scrollComp.getScrollableBlockIncrement(viewRect, orientation, direction); if (direction < 0) { scrollMin = Math.max(scrollMin, toScroll.getValue() - blockIncr); } else { scrollMax = Math.min(scrollMax, toScroll.getValue() + blockIncr); } } for (int i = 0; i < units; i++) { int unitIncr = scrollComp.getScrollableUnitIncrement(viewRect, orientation, direction); if (orientation == SwingConstants.VERTICAL) { if (direction < 0) { viewRect.y -= unitIncr; if (viewRect.y <= scrollMin) { viewRect.y = scrollMin; break; } } else { // (direction > 0 viewRect.y += unitIncr; if (viewRect.y >= scrollMax) { viewRect.y = scrollMax; break; } } } else { if ((leftToRight && direction < 0) || (!leftToRight && direction > 0)) { viewRect.x -= unitIncr; if (leftToRight) { if (viewRect.x < scrollMin) { viewRect.x = scrollMin; break; } } } else if ((leftToRight && direction > 0) || (!leftToRight && direction < 0)) { viewRect.x += unitIncr; if (leftToRight) { if (viewRect.x > scrollMax) { viewRect.x = scrollMax; break; } } } else { assert false : "Non-sensical ComponentOrientation / scroll direction"; } } } if (orientation == SwingConstants.VERTICAL) { toScroll.setValue(viewRect.y); } else { if (leftToRight) { toScroll.setValue(viewRect.x); } else { int newPos = toScroll.getValue() - (viewRect.x - startingX); if (newPos < scrollMin) { newPos = scrollMin; } else if (newPos > scrollMax) { newPos = scrollMax; } toScroll.setValue(newPos); } } } else { int delta; int limit = -1; if (limitScroll) { if (direction < 0) { limit = toScroll.getValue() - toScroll.getBlockIncrement(direction); } else { limit = toScroll.getValue() + toScroll.getBlockIncrement(direction); } } for (int i = 0; i < units; i++) { if (direction > 0) { delta = toScroll.getUnitIncrement(direction); } else { delta = -toScroll.getUnitIncrement(direction); } int oldValue = toScroll.getValue(); int newValue = oldValue + delta; if (delta > 0 && newValue < oldValue) { newValue = toScroll.getMaximum(); } else if (delta < 0 && newValue > oldValue) { newValue = toScroll.getMinimum(); } if (oldValue == newValue) { break; } if (limitScroll && i > 0) { assert limit != -1; if ((direction < 0 && newValue < limit) || (direction > 0 && newValue > limit)) { break; } } toScroll.setValue(newValue); } } } else if (e.getScrollType() == MouseWheelEvent.WHEEL_BLOCK_SCROLL) { int oldValue = toScroll.getValue(); int blockIncrement = toScroll.getBlockIncrement(direction); int delta = blockIncrement * ((direction > 0) ? +1 : -1); int newValue = oldValue + delta; if (delta > 0 && newValue < oldValue) { newValue = toScroll.getMaximum(); } else if (delta < 0 && newValue > oldValue) { newValue = toScroll.getMinimum(); } toScroll.setValue(newValue); } } } e.consume(); } }); textArea.addMouseMotionListener(new MouseMotionAdapter() { private boolean isLinkLabelPrev = false; private String prevLinkText = null; @Override public synchronized void mouseMoved(MouseEvent e) { String linkText = null; boolean isLinkLabel = false; boolean isCtrlDown = (e.getModifiersEx() & Keymap.ctrlDownModifier()) != 0; if (isCtrlDown) { linkText = createLinkLabel(e); isLinkLabel = linkText != null; } if (isCtrlDown && isWaitForLinksCursor) { textArea.setCursor(new Cursor(Cursor.WAIT_CURSOR)); } else if (textArea.getCursor().getType() == Cursor.WAIT_CURSOR) { textArea.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } JLabel label = OpenFile.this.mainWindow.getLabel(); if (isLinkLabel && isLinkLabelPrev) { if (!linkText.equals(prevLinkText)) { setLinkLabel(label, linkText); } } else if (isLinkLabel && !isLinkLabelPrev) { setLinkLabel(label, linkText); } else if (!isLinkLabel && isLinkLabelPrev) { setLinkLabel(label, null); } isLinkLabelPrev = isLinkLabel; prevLinkText = linkText; } private void setLinkLabel(JLabel label, String text) { String current = label.getText(); if (text == null && current != null) if (current.startsWith("Navigating:") || current.startsWith("Cannot navigate:")) return; label.setText(text != null ? text : "Complete"); } private String createLinkLabel(MouseEvent e) { int offs = textArea.viewToModel(e.getPoint()); if (isNavigationLinksValid) { return getLinkDescriptionForOffset(offs); } return null; } }); } public void setContent(String content) { textArea.setText(content); } public void decompile() { this.invalidateContent(); // synchronized: do not accept changes from menu while running synchronized (settings) { if (Languages.java().getName().equals(settings.getLanguage().getName())) { decompileWithNavigationLinks(); } else { decompileWithoutLinks(); } } } private void decompileWithoutLinks() { this.invalidateContent(); isNavigationLinksValid = false; textArea.setHyperlinksEnabled(false); StringWriter stringwriter = new StringWriter(); PlainTextOutput plainTextOutput = new PlainTextOutput(stringwriter); plainTextOutput.setUnicodeOutputEnabled(decompilationOptions.getSettings().isUnicodeOutputEnabled()); settings.getLanguage().decompileType(type, plainTextOutput, decompilationOptions); setContentPreserveLastScrollPosition(stringwriter.toString()); this.isContentValid = true; } private void decompileWithNavigationLinks() { this.invalidateContent(); DecompilerLinkProvider newLinkProvider = new DecompilerLinkProvider(); newLinkProvider.setDecompilerReferences(metadataSystem, settings, decompilationOptions); newLinkProvider.setType(type); linkProvider = newLinkProvider; linkProvider.generateContent(); setContentPreserveLastScrollPosition(linkProvider.getTextContent()); this.isContentValid = true; enableLinks(); } private void setContentPreserveLastScrollPosition(final String content) { final Double scrollPercent = lastScrollPercent; if (scrollPercent != null && initialNavigationLink == null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { textArea.setText(content); restoreScrollPosition(scrollPercent); } }); } else { textArea.setText(content); } } private void restoreScrollPosition(final double position) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JScrollBar verticalScrollbar = scrollPane.getVerticalScrollBar(); if (verticalScrollbar == null) return; int scrollMax = verticalScrollbar.getMaximum() - verticalScrollbar.getMinimum(); long newScrollValue = Math.round(position * scrollMax) + verticalScrollbar.getMinimum(); if (newScrollValue < verticalScrollbar.getMinimum()) newScrollValue = verticalScrollbar.getMinimum(); if (newScrollValue > verticalScrollbar.getMaximum()) newScrollValue = verticalScrollbar.getMaximum(); verticalScrollbar.setValue((int) newScrollValue); } }); } private void enableLinks() { if (initialNavigationLink != null) { doEnableLinks(); } else { new Thread(new Runnable() { @Override public void run() { try { isWaitForLinksCursor = true; doEnableLinks(); } finally { isWaitForLinksCursor = false; resetCursor(); } } }).start(); } } private void resetCursor() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { textArea.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } }); } private void doEnableLinks() { isNavigationLinksValid = false; linkProvider.processLinks(); buildSelectionToUniqueStrTreeMap(); clearLinksCache(); isNavigationLinksValid = true; textArea.setHyperlinksEnabled(true); warmUpWithFirstLink(); } private void warmUpWithFirstLink() { if (selectionToUniqueStrTreeMap.keySet().size() > 0) { Selection selection = selectionToUniqueStrTreeMap.keySet().iterator().next(); getLinkDescriptionForOffset(selection.from); } } public void clearLinksCache() { try { isNavigableCache.clear(); readableLinksCache.clear(); } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } private void buildSelectionToUniqueStrTreeMap() { TreeMap<Selection, String> treeMap = new TreeMap<>(); Map<String, Selection> definitionToSelectionMap = linkProvider.getDefinitionToSelectionMap(); Map<String, Set<Selection>> referenceToSelectionsMap = linkProvider.getReferenceToSelectionsMap(); for (String key : definitionToSelectionMap.keySet()) { Selection selection = definitionToSelectionMap.get(key); treeMap.put(selection, key); } for (String key : referenceToSelectionsMap.keySet()) { for (Selection selection : referenceToSelectionsMap.get(key)) { treeMap.put(selection, key); } } selectionToUniqueStrTreeMap = treeMap; } private Selection getSelectionForOffset(int offset) { if (isNavigationLinksValid) { Selection offsetSelection = new Selection(offset, offset); Selection floorSelection = selectionToUniqueStrTreeMap.floorKey(offsetSelection); if (floorSelection != null && floorSelection.from <= offset && floorSelection.to > offset) { return floorSelection; } } return null; } private String getUniqueStrForOffset(int offset) { Selection selection = getSelectionForOffset(offset); if (selection != null) { String uniqueStr = selectionToUniqueStrTreeMap.get(selection); if (this.isLinkNavigable(uniqueStr) && this.getLinkDescription(uniqueStr) != null) { return uniqueStr; } } return null; } private Integer getSelectionFromForOffset(int offset) { Selection selection = getSelectionForOffset(offset); if (selection != null) { return selection.from; } return null; } private String getLinkDescriptionForOffset(int offset) { String uniqueStr = getUniqueStrForOffset(offset); if (uniqueStr != null) { String description = this.getLinkDescription(uniqueStr); if (description != null) { return description; } } return null; } private boolean isLinkNavigable(String uniqueStr) { try { Boolean isNavigableCached = isNavigableCache.get(uniqueStr); if (isNavigableCached != null) return isNavigableCached; boolean isNavigable = linkProvider.isLinkNavigable(uniqueStr); isNavigableCache.put(uniqueStr, isNavigable); return isNavigable; } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } return false; } private String getLinkDescription(String uniqueStr) { try { String descriptionCached = readableLinksCache.get(uniqueStr); if (descriptionCached != null) return descriptionCached; String description = linkProvider.getLinkDescription(uniqueStr); if (description != null && description.trim().length() > 0) { readableLinksCache.put(uniqueStr, description); return description; } } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } return null; } private void onNavigationClicked(String clickedReferenceUniqueStr) { if (isLocallyNavigable(clickedReferenceUniqueStr)) { onLocalNavigationRequest(clickedReferenceUniqueStr); } else if (linkProvider.isLinkNavigable(clickedReferenceUniqueStr)) { onOutboundNavigationRequest(clickedReferenceUniqueStr); } else { JLabel label = this.mainWindow.getLabel(); if (label == null) return; String[] linkParts = clickedReferenceUniqueStr.split("\\|"); if (linkParts.length <= 1) { label.setText("Cannot navigate: " + clickedReferenceUniqueStr); return; } String destinationTypeStr = linkParts[1]; label.setText("Cannot navigate: " + destinationTypeStr.replaceAll("/", ".")); } } private boolean isLocallyNavigable(String uniqueStr) { return linkProvider.getDefinitionToSelectionMap().keySet().contains(uniqueStr); } private void onLocalNavigationRequest(String uniqueStr) { try { Selection selection = linkProvider.getDefinitionToSelectionMap().get(uniqueStr); doLocalNavigation(selection); } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } private void doLocalNavigation(Selection selection) { try { textArea.requestFocusInWindow(); if (selection != null) { textArea.setSelectionStart(selection.from); textArea.setSelectionEnd(selection.to); scrollToSelection(selection.from); } else { textArea.setSelectionStart(0); textArea.setSelectionEnd(0); } } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } private void scrollToSelection(final int selectionBeginningOffset) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { int fullHeight = textArea.getBounds().height; int viewportHeight = textArea.getVisibleRect().height; int viewportLineCount = viewportHeight / textArea.getLineHeight(); int selectionLineNum = textArea.getLineOfOffset(selectionBeginningOffset); int upperMarginToScroll = Math.round(viewportLineCount * 0.29f); int upperLineToSet = selectionLineNum - upperMarginToScroll; int currentUpperLine = textArea.getVisibleRect().y / textArea.getLineHeight(); if (selectionLineNum <= currentUpperLine + 2 || selectionLineNum >= currentUpperLine + viewportLineCount - 4) { Rectangle rectToScroll = new Rectangle(); rectToScroll.x = 0; rectToScroll.width = 1; rectToScroll.y = Math.max(upperLineToSet * textArea.getLineHeight(), 0); rectToScroll.height = Math.min(viewportHeight, fullHeight - rectToScroll.y); textArea.scrollRectToVisible(rectToScroll); } } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } }); } private void onOutboundNavigationRequest(String uniqueStr) { mainWindow.onNavigationRequest(uniqueStr); } public void setDecompilerReferences(MetadataSystem metadataSystem, DecompilerSettings settings, DecompilationOptions decompilationOptions) { this.metadataSystem = metadataSystem; this.settings = settings; this.decompilationOptions = decompilationOptions; } public TypeDefinition getType() { return type; } public void setType(TypeDefinition type) { this.type = type; } public boolean isContentValid() { return isContentValid; } public void invalidateContent() { try { this.setContent(""); } finally { this.isContentValid = false; this.isNavigationLinksValid = false; } } public void resetScrollPosition() { lastScrollPercent = null; } public void setInitialNavigationLink(String initialNavigationLink) { this.initialNavigationLink = initialNavigationLink; } public void onAddedToScreen() { try { if (initialNavigationLink != null) { onLocalNavigationRequest(initialNavigationLink); } else if (isFirstTimeRun) { // warm up scrolling isFirstTimeRun = false; doLocalNavigation(new Selection(0, 0)); } } finally { initialNavigationLink = null; } } /** * sun.swing.CachedPainter holds on OpenFile for a while even after * JTabbedPane.remove(component) */ public void close() { linkProvider = null; type = null; invalidateContent(); clearLinksCache(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((path == null) ? 0 : path.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; OpenFile other = (OpenFile) obj; if (path == null) { if (other.path != null) return false; } else if (!path.equals(other.path)) return false; return true; } }