1 /* 2 * Copyright (c) 2008-2019 Emmanuel Dupuy. 3 * This project is distributed under the GPLv3 license. 4 * This is a Copyleft license that gives the user the right to use, 5 * copy and modify the code freely for non-commercial purposes. 6 */ 7 8 package org.jd.gui.view.component; 9 10 import org.fife.ui.rsyntaxtextarea.*; 11 import org.fife.ui.rsyntaxtextarea.folding.FoldManager; 12 import org.fife.ui.rtextarea.*; 13 import org.jd.gui.api.feature.ContentSearchable; 14 import org.jd.gui.api.feature.LineNumberNavigable; 15 import org.jd.gui.api.feature.PreferencesChangeListener; 16 import org.jd.gui.api.feature.UriOpenable; 17 import org.jd.gui.util.exception.ExceptionUtil; 18 19 import javax.swing.*; 20 import javax.swing.text.BadLocationException; 21 import java.awt.*; 22 import java.awt.event.KeyEvent; 23 import java.awt.event.MouseAdapter; 24 import java.awt.event.MouseEvent; 25 import java.awt.event.MouseWheelListener; 26 import java.io.IOException; 27 import java.io.UnsupportedEncodingException; 28 import java.net.URI; 29 import java.net.URLDecoder; 30 import java.util.HashMap; 31 import java.util.Map; 32 33 public class AbstractTextPage extends JPanel implements LineNumberNavigable, ContentSearchable, UriOpenable, PreferencesChangeListener { 34 protected static final String FONT_SIZE_KEY = "ViewerPreferences.fontSize"; 35 36 protected static final ImageIcon COLLAPSED_ICON = new ImageIcon(AbstractTextPage.class.getClassLoader().getResource("org/jd/gui/images/plus.png")); 37 protected static final ImageIcon EXPANDED_ICON = new ImageIcon(AbstractTextPage.class.getClassLoader().getResource("org/jd/gui/images/minus.png")); 38 39 protected static final Color DOUBLE_CLICK_HIGHLIGHT_COLOR = new Color(0x66ff66); 40 protected static final Color SEARCH_HIGHLIGHT_COLOR = new Color(0xffff66); 41 protected static final Color SELECT_HIGHLIGHT_COLOR = new Color(0xF49810); 42 43 protected static final RSyntaxTextAreaEditorKit.DecreaseFontSizeAction DECREASE_FONT_SIZE_ACTION = new RSyntaxTextAreaEditorKit.DecreaseFontSizeAction(); 44 protected static final RSyntaxTextAreaEditorKit.IncreaseFontSizeAction INCREASE_FONT_SIZE_ACTION = new RSyntaxTextAreaEditorKit.IncreaseFontSizeAction(); 45 46 protected RSyntaxTextArea textArea; 47 protected RTextScrollPane scrollPane; 48 49 protected Map<String, String> preferences; 50 AbstractTextPage()51 public AbstractTextPage() { 52 super(new BorderLayout()); 53 54 textArea = newSyntaxTextArea(); 55 textArea.setSyntaxEditingStyle(getSyntaxStyle()); 56 textArea.setCodeFoldingEnabled(true); 57 textArea.setAntiAliasingEnabled(true); 58 textArea.setCaretPosition(0); 59 textArea.setEditable(false); 60 textArea.setDropTarget(null); 61 textArea.setPopupMenu(null); 62 textArea.addMouseListener(new MouseAdapter() { 63 public void mouseClicked(MouseEvent e) { 64 if (e.getClickCount() == 2) { 65 textArea.setMarkAllHighlightColor(DOUBLE_CLICK_HIGHLIGHT_COLOR); 66 SearchEngine.markAll(textArea, newSearchContext(textArea.getSelectedText(), true, true, true, false)); 67 } 68 } 69 }); 70 71 KeyStroke ctrlA = KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); 72 KeyStroke ctrlC = KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); 73 KeyStroke ctrlV = KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); 74 InputMap inputMap = textArea.getInputMap(); 75 inputMap.put(ctrlA, "none"); 76 inputMap.put(ctrlC, "none"); 77 inputMap.put(ctrlV, "none"); 78 79 try { 80 Theme theme = Theme.load(getClass().getClassLoader().getResourceAsStream("rsyntaxtextarea/themes/eclipse.xml")); 81 theme.apply(textArea); 82 } catch (IOException e) { 83 assert ExceptionUtil.printStackTrace(e); 84 } 85 86 scrollPane = new RTextScrollPane(textArea); 87 scrollPane.setFoldIndicatorEnabled(true); 88 scrollPane.setFont(textArea.getFont()); 89 90 final MouseWheelListener[] mouseWheelListeners = scrollPane.getMouseWheelListeners(); 91 92 // Remove default listeners 93 for (MouseWheelListener listener : mouseWheelListeners) { 94 scrollPane.removeMouseWheelListener(listener); 95 } 96 97 scrollPane.addMouseWheelListener(e -> { 98 if ((e.getModifiers() & (Event.META_MASK|Event.CTRL_MASK)) != 0) { 99 int x = e.getX() + scrollPane.getX() - textArea.getX(); 100 int y = e.getY() + scrollPane.getY() - textArea.getY(); 101 int offset = textArea.viewToModel(new Point(x, y)); 102 103 // Update font size 104 if (e.getWheelRotation() > 0) { 105 DECREASE_FONT_SIZE_ACTION.actionPerformedImpl(null, textArea); 106 } else { 107 INCREASE_FONT_SIZE_ACTION.actionPerformedImpl(null, textArea); 108 } 109 110 // Save preferences 111 if (preferences != null) { 112 preferences.put(FONT_SIZE_KEY, String.valueOf(textArea.getFont().getSize())); 113 } 114 115 try { 116 Rectangle newRectangle = textArea.modelToView(offset); 117 int newY = newRectangle.y + (newRectangle.height >> 1); 118 119 // Scroll 120 Point viewPosition = scrollPane.getViewport().getViewPosition(); 121 viewPosition.y = Math.max(viewPosition.y +newY - y, 0); 122 scrollPane.getViewport().setViewPosition(viewPosition); 123 } catch (BadLocationException ee) { 124 assert ExceptionUtil.printStackTrace(ee); 125 } 126 } else { 127 // Call default listeners 128 for (MouseWheelListener listener : mouseWheelListeners) { 129 listener.mouseWheelMoved(e); 130 } 131 } 132 }); 133 134 Gutter gutter = scrollPane.getGutter(); 135 gutter.setFoldIcons(COLLAPSED_ICON, EXPANDED_ICON); 136 gutter.setFoldIndicatorForeground(gutter.getBorderColor()); 137 138 add(scrollPane, BorderLayout.CENTER); 139 add(new RoundMarkErrorStrip(textArea), BorderLayout.LINE_END); 140 } 141 newSyntaxTextArea()142 protected RSyntaxTextArea newSyntaxTextArea() { return new RSyntaxTextArea(); } 143 getText()144 public String getText() { return textArea.getText(); } 145 getScrollPane()146 public JScrollPane getScrollPane() { 147 return scrollPane; 148 } 149 setText(String text)150 public void setText(String text) { 151 textArea.setText(text); 152 textArea.setCaretPosition(0); 153 } 154 getSyntaxStyle()155 public String getSyntaxStyle() { return SyntaxConstants.SYNTAX_STYLE_NONE; } 156 157 /** 158 * @see org.fife.ui.rsyntaxtextarea.RSyntaxUtilities#selectAndPossiblyCenter 159 * Force center and do not select 160 */ setCaretPositionAndCenter(DocumentRange range)161 public void setCaretPositionAndCenter(DocumentRange range) { 162 final int start = range.getStartOffset(); 163 final int end = range.getEndOffset(); 164 boolean foldsExpanded = false; 165 FoldManager fm = textArea.getFoldManager(); 166 167 if (fm.isCodeFoldingSupportedAndEnabled()) { 168 foldsExpanded = fm.ensureOffsetNotInClosedFold(start); 169 foldsExpanded |= fm.ensureOffsetNotInClosedFold(end); 170 } 171 172 if (!foldsExpanded) { 173 try { 174 Rectangle rec = textArea.modelToView(start); 175 176 if (rec != null) { 177 // Visible 178 setCaretPositionAndCenter(start, end, rec); 179 } else { 180 // Not visible yet 181 SwingUtilities.invokeLater(() -> { 182 try { 183 Rectangle r = textArea.modelToView(start); 184 if (r != null) { 185 setCaretPositionAndCenter(start, end, r); 186 } 187 } catch (BadLocationException e) { 188 assert ExceptionUtil.printStackTrace(e); 189 } 190 }); 191 } 192 } catch (BadLocationException e) { 193 assert ExceptionUtil.printStackTrace(e); 194 } 195 } 196 } 197 setCaretPositionAndCenter(int start, int end, Rectangle r)198 protected void setCaretPositionAndCenter(int start, int end, Rectangle r) { 199 if (end != start) { 200 try { 201 r = r.union(textArea.modelToView(end)); 202 } catch (BadLocationException e) { 203 assert ExceptionUtil.printStackTrace(e); 204 } 205 } 206 207 Rectangle visible = textArea.getVisibleRect(); 208 209 // visible.x = r.x - (visible.width - r.width) / 2; 210 visible.y = r.y - (visible.height - r.height) / 2; 211 212 Rectangle bounds = textArea.getBounds(); 213 Insets i = textArea.getInsets(); 214 //bounds.x = i.left; 215 bounds.y = i.top; 216 //bounds.width -= i.left + i.right; 217 bounds.height -= i.top + i.bottom; 218 219 //if (visible.x < bounds.x) { 220 // visible.x = bounds.x; 221 //} 222 //if (visible.x + visible.width > bounds.x + bounds.width) { 223 // visible.x = bounds.x + bounds.width - visible.width; 224 //} 225 if (visible.y < bounds.y) { 226 visible.y = bounds.y; 227 } 228 if (visible.y + visible.height > bounds.y + bounds.height) { 229 visible.y = bounds.y + bounds.height - visible.height; 230 } 231 232 textArea.scrollRectToVisible(visible); 233 textArea.setCaretPosition(start); 234 } 235 236 // --- LineNumberNavigable --- // getMaximumLineNumber()237 public int getMaximumLineNumber() { 238 try { 239 return textArea.getLineOfOffset(textArea.getDocument().getLength()) + 1; 240 } catch (BadLocationException e) { 241 assert ExceptionUtil.printStackTrace(e); 242 return 0; 243 } 244 } 245 goToLineNumber(int lineNumber)246 public void goToLineNumber(int lineNumber) { 247 try { 248 textArea.setCaretPosition(textArea.getLineStartOffset(lineNumber-1)); 249 } catch (BadLocationException e) { 250 assert ExceptionUtil.printStackTrace(e); 251 } 252 } 253 checkLineNumber(int lineNumber)254 public boolean checkLineNumber(int lineNumber) { return true; } 255 256 // --- ContentSearchable --- // highlightText(String text, boolean caseSensitive)257 public boolean highlightText(String text, boolean caseSensitive) { 258 if (text.length() > 1) { 259 textArea.setMarkAllHighlightColor(SEARCH_HIGHLIGHT_COLOR); 260 textArea.setCaretPosition(textArea.getSelectionStart()); 261 262 SearchContext context = newSearchContext(text, caseSensitive, false, true, false); 263 SearchResult result = SearchEngine.find(textArea, context); 264 265 if (!result.wasFound()) { 266 textArea.setCaretPosition(0); 267 result = SearchEngine.find(textArea, context); 268 } 269 270 return result.wasFound(); 271 } else { 272 return true; 273 } 274 } 275 findNext(String text, boolean caseSensitive)276 public void findNext(String text, boolean caseSensitive) { 277 if (text.length() > 1) { 278 textArea.setMarkAllHighlightColor(SEARCH_HIGHLIGHT_COLOR); 279 280 SearchContext context = newSearchContext(text, caseSensitive, false, true, false); 281 SearchResult result = SearchEngine.find(textArea, context); 282 283 if (!result.wasFound()) { 284 textArea.setCaretPosition(0); 285 SearchEngine.find(textArea, context); 286 } 287 } 288 } 289 findPrevious(String text, boolean caseSensitive)290 public void findPrevious(String text, boolean caseSensitive) { 291 if (text.length() > 1) { 292 textArea.setMarkAllHighlightColor(SEARCH_HIGHLIGHT_COLOR); 293 294 SearchContext context = newSearchContext(text, caseSensitive, false, false, false); 295 SearchResult result = SearchEngine.find(textArea, context); 296 297 if (!result.wasFound()) { 298 textArea.setCaretPosition(textArea.getDocument().getLength()); 299 SearchEngine.find(textArea, context); 300 } 301 } 302 } 303 newSearchContext(String searchFor, boolean matchCase, boolean wholeWord, boolean searchForward, boolean regexp)304 protected SearchContext newSearchContext(String searchFor, boolean matchCase, boolean wholeWord, boolean searchForward, boolean regexp) { 305 SearchContext context = new SearchContext(searchFor, matchCase); 306 context.setMarkAll(true); 307 context.setWholeWord(wholeWord); 308 context.setSearchForward(searchForward); 309 context.setRegularExpression(regexp); 310 return context; 311 } 312 313 // --- UriOpenable --- // openUri(URI uri)314 public boolean openUri(URI uri) { 315 String query = uri.getQuery(); 316 317 if (query != null) { 318 Map<String, String> parameters = parseQuery(query); 319 320 if (parameters.containsKey("lineNumber")) { 321 String lineNumber = parameters.get("lineNumber"); 322 323 try { 324 goToLineNumber(Integer.parseInt(lineNumber)); 325 return true; 326 } catch (NumberFormatException e) { 327 assert ExceptionUtil.printStackTrace(e); 328 } 329 } else if (parameters.containsKey("position")) { 330 String position = parameters.get("position"); 331 332 try { 333 int pos = Integer.parseInt(position); 334 if (textArea.getDocument().getLength() > pos) { 335 setCaretPositionAndCenter(new DocumentRange(pos, pos)); 336 return true; 337 } 338 } catch (NumberFormatException e) { 339 assert ExceptionUtil.printStackTrace(e); 340 } 341 } else if (parameters.containsKey("highlightFlags")) { 342 String highlightFlags = parameters.get("highlightFlags"); 343 344 if ((highlightFlags.indexOf('s') != -1) && parameters.containsKey("highlightPattern")) { 345 textArea.setMarkAllHighlightColor(SELECT_HIGHLIGHT_COLOR); 346 textArea.setCaretPosition(0); 347 348 // Highlight all 349 String searchFor = createRegExp(parameters.get("highlightPattern")); 350 SearchContext context = newSearchContext(searchFor, true, false, true, true); 351 SearchResult result = SearchEngine.find(textArea, context); 352 353 if (result.getMatchRange() != null) { 354 textArea.setCaretPosition(result.getMatchRange().getStartOffset()); 355 } 356 357 return true; 358 } 359 } 360 } 361 362 return false; 363 } 364 parseQuery(String query)365 protected Map<String, String> parseQuery(String query) { 366 HashMap<String, String> parameters = new HashMap<>(); 367 368 // Parse parameters 369 try { 370 for (String param : query.split("&")) { 371 int index = param.indexOf('='); 372 373 if (index == -1) { 374 parameters.put(URLDecoder.decode(param, "UTF-8"), ""); 375 } else { 376 String key = param.substring(0, index); 377 String value = param.substring(index + 1); 378 parameters.put(URLDecoder.decode(key, "UTF-8"), URLDecoder.decode(value, "UTF-8")); 379 } 380 } 381 } catch (UnsupportedEncodingException e) { 382 assert ExceptionUtil.printStackTrace(e); 383 } 384 385 return parameters; 386 } 387 388 /** 389 * Create a simple regular expression 390 * 391 * Rules: 392 * '*' matchTypeEntries 0 ou N characters 393 * '?' matchTypeEntries 1 character 394 */ createRegExp(String pattern)395 public static String createRegExp(String pattern) { 396 int patternLength = pattern.length(); 397 StringBuilder sbPattern = new StringBuilder(patternLength * 2); 398 399 for (int i = 0; i < patternLength; i++) { 400 char c = pattern.charAt(i); 401 402 if (c == '*') { 403 sbPattern.append(".*"); 404 } else if (c == '?') { 405 sbPattern.append('.'); 406 } else if (c == '.') { 407 sbPattern.append("\\."); 408 } else { 409 sbPattern.append(c); 410 } 411 } 412 413 return sbPattern.toString(); 414 } 415 416 // --- PreferencesChangeListener --- // preferencesChanged(Map<String, String> preferences)417 public void preferencesChanged(Map<String, String> preferences) { 418 String fontSize = preferences.get(FONT_SIZE_KEY); 419 420 if (fontSize != null) { 421 try { 422 textArea.setFont(textArea.getFont().deriveFont(Float.parseFloat(fontSize))); 423 } catch (Exception e) { 424 assert ExceptionUtil.printStackTrace(e); 425 } 426 } 427 428 this.preferences = preferences; 429 } 430 } 431