1 19 20 package org.netbeans.lib.editor.codetemplates; 21 22 import java.awt.event.ActionEvent ; 23 import java.awt.event.KeyEvent ; 24 import java.awt.event.KeyListener ; 25 import java.util.ArrayList ; 26 import java.util.Collection ; 27 import java.util.Collections ; 28 import java.util.Iterator ; 29 import java.util.List ; 30 import javax.swing.Action ; 31 import javax.swing.ActionMap ; 32 import javax.swing.KeyStroke ; 33 import javax.swing.event.DocumentEvent ; 34 import javax.swing.event.DocumentListener ; 35 import javax.swing.text.BadLocationException ; 36 import javax.swing.text.Caret ; 37 import javax.swing.text.Document ; 38 import javax.swing.text.JTextComponent ; 39 import javax.swing.text.Position ; 40 import org.netbeans.editor.BaseDocument; 41 import org.netbeans.editor.BaseKit; 42 import org.netbeans.editor.DrawLayer; 43 import org.netbeans.editor.EditorUI; 44 import org.netbeans.editor.Formatter; 45 import org.netbeans.editor.Utilities; 46 import org.netbeans.lib.editor.codetemplates.api.CodeTemplate; 47 import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateInsertRequest; 48 import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateParameter; 49 import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateProcessor; 50 import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateProcessorFactory; 51 import org.netbeans.lib.editor.util.CharSequenceUtilities; 52 import org.netbeans.lib.editor.util.swing.DocumentUtilities; 53 import org.netbeans.lib.editor.util.swing.MutablePositionRegion; 54 import org.netbeans.lib.editor.util.swing.PositionRegion; 55 import org.openide.ErrorManager; 56 57 63 public final class CodeTemplateInsertHandler 64 implements DocumentListener , KeyListener { 65 66 69 private static final Object EDITING_TEMPLATE_DOC_PROPERTY = "processing-code-template"; 71 private final CodeTemplate codeTemplate; 72 73 private final JTextComponent component; 74 75 private final List processors; 76 77 private String parametrizedText; 78 79 private ParametrizedTextParser parametrizedTextParser; 80 81 private String insertText; 82 83 private List allParameters; 84 85 private List allParametersUnmodifiable; 86 87 private List masters; 88 89 private List mastersUnmodifiable; 90 91 private List editableMasters; 92 93 private CodeTemplateInsertRequest request; 94 95 private boolean inserted; 96 97 private boolean released; 98 99 private Position caretPosition; 100 101 private int activeMasterIndex; 102 103 private ActionMap componentOrigActionMap; 104 105 private List drawLayers; 106 107 private Document doc; 108 109 private MutablePositionRegion positionRegion; 110 111 118 private boolean nestedTemplateExpanding; 119 120 125 private CodeTemplateParameterImpl apiSetValueParamImpl; 126 127 130 private int lastActiveRegionStartOffset; 131 132 135 private int lastActiveRegionEndOffset; 136 137 141 private boolean activeMasterModified; 142 143 146 private boolean syncingDocModification; 147 148 public CodeTemplateInsertHandler(CodeTemplate codeTemplate, 149 JTextComponent component, Collection processorFactories) { 150 this.codeTemplate = codeTemplate; 151 this.component = component; 152 153 Position zeroPos = PositionRegion.createFixedPosition(0); 154 this.positionRegion = new MutablePositionRegion(zeroPos, zeroPos); 155 156 CodeTemplateInsertRequest.class.getClass().getName(); 158 159 this.request = CodeTemplateSpiPackageAccessor.get(). 160 createInsertRequest(this); 161 162 processors = new ArrayList (); 163 for (Iterator it = processorFactories.iterator(); it.hasNext();) { 164 CodeTemplateProcessorFactory factory = (CodeTemplateProcessorFactory)it.next(); 165 processors.add(factory.createProcessor(this.request)); 166 } 167 168 setParametrizedText(codeTemplate.getParametrizedText()); 169 } 170 171 public CodeTemplate getCodeTemplate() { 172 return codeTemplate; 173 } 174 175 public JTextComponent getComponent() { 176 return component; 177 } 178 179 public CodeTemplateInsertRequest getRequest() { 180 return request; 181 } 182 183 public synchronized boolean isInserted() { 184 return inserted; 185 } 186 187 private synchronized void markInserted() { 188 this.inserted = true; 189 resetCachedInsertText(); 190 } 191 192 public synchronized boolean isReleased() { 193 return released; 194 } 195 196 public String getParametrizedText() { 197 return parametrizedText; 198 } 199 200 public void setParametrizedText(String parametrizedText) { 201 this.parametrizedText = parametrizedText; 202 parseParametrizedText(); 203 } 204 205 public int getInsertOffset() { 206 return positionRegion.getStartOffset(); 207 } 208 209 public String getInsertText() { 210 if (inserted) { 211 try { 212 int startOffset = positionRegion.getStartOffset(); 213 return doc.getText(startOffset, positionRegion.getEndOffset() - startOffset); 214 } catch (BadLocationException e) { 215 ErrorManager.getDefault().notify(e); 216 return ""; 217 } 218 219 } else { checkInsertTextBuilt(); 221 return insertText; 222 } 223 } 224 225 public List getAllParameters() { 226 return allParametersUnmodifiable; 227 } 228 229 public List getMasterParameters() { 230 return mastersUnmodifiable; 231 } 232 233 public void processTemplate() { 234 for (Iterator it = processors.iterator(); it.hasNext();) { 236 CodeTemplateProcessor processor = (CodeTemplateProcessor)it.next(); 237 processor.updateDefaultValues(); 238 } 239 240 insertTemplate(); 242 243 installActions(); 245 } 246 247 void checkInsertTextBuilt() { 248 if (insertText == null) { 249 insertText = buildInsertText(); 250 } 251 } 252 253 void resetCachedInsertText() { 254 insertText = null; 255 } 256 257 CodeTemplateParameter getActiveMaster() { 258 return (activeMasterIndex < editableMasters.size()) 259 ? (CodeTemplateParameter)editableMasters.get(activeMasterIndex) 260 : null; 261 } 262 263 CodeTemplateParameterImpl getActiveMasterImpl() { 264 CodeTemplateParameter master = getActiveMaster(); 265 return (master != null) ? paramImpl(master) : null; 266 } 267 268 public void insertTemplate() { 269 doc = component.getDocument(); 270 nestedTemplateExpanding = (Boolean.TRUE.equals(doc.getProperty( 271 EDITING_TEMPLATE_DOC_PROPERTY))); 272 273 String completeInsertString = getInsertText(); 274 275 if (doc instanceof BaseDocument) { 276 ((BaseDocument)doc).atomicLock(); 277 } 278 try { 279 Caret caret = component.getCaret(); 281 if (caret.isSelectionVisible()) { 282 int removeOffset = component.getSelectionStart(); 283 int removeLength = component.getSelectionEnd() - removeOffset; 284 doc.remove(removeOffset, removeLength); 285 } 286 287 int insertOffset = component.getCaretPosition(); 289 int docLen = doc.getLength(); 290 doc.insertString(insertOffset, completeInsertString, null); 291 292 for (Iterator it = request.getMasterParameters().iterator(); it.hasNext();) { 294 CodeTemplateParameter parameter = (CodeTemplateParameter)it.next(); 295 296 if (CodeTemplateParameter.CURSOR_PARAMETER_NAME.equals(parameter.getName())) { 297 caretPosition = doc.createPosition(insertOffset + parameter.getInsertTextOffset()); 298 CodeTemplateParameterImpl paramImpl = CodeTemplateParameterImpl.get(parameter); 299 paramImpl.resetPositions(caretPosition, caretPosition); 300 } else { List parameterRegions = new ArrayList (4); 302 addParameterRegion(parameterRegions, parameter, doc, insertOffset); 303 for (Iterator slaveIt = parameter.getSlaves().iterator(); slaveIt.hasNext();) { 304 CodeTemplateParameter slaveParameter = (CodeTemplateParameter)slaveIt.next(); 305 addParameterRegion(parameterRegions, slaveParameter, doc, insertOffset); 306 } 307 308 SyncDocumentRegion region = new SyncDocumentRegion(doc, parameterRegions); 309 paramImpl(parameter).setRegion(region); 310 } 311 } 312 313 positionRegion.reset(doc.createPosition(insertOffset), 314 doc.createPosition(insertOffset + (doc.getLength() - docLen))); 315 316 if (caretPosition == null) { caretPosition = doc.createPosition(insertOffset + completeInsertString.length()); 318 } 319 320 if (parametrizedText.indexOf('\n') != -1 && doc instanceof BaseDocument) { 321 BaseDocument bdoc = (BaseDocument)doc; 322 Formatter formatter = bdoc.getFormatter(); 323 if (formatter != null) { 324 formatter.reformat(bdoc, insertOffset, 325 insertOffset + completeInsertString.length()); 326 } 327 } 328 329 } catch (BadLocationException e) { 330 ErrorManager.getDefault().notify(e); 331 } finally { 332 if (doc instanceof BaseDocument) { 333 ((BaseDocument)doc).atomicUnlock(); 334 } 335 336 markInserted(); 337 } 338 } 339 340 public void installActions() { 341 if (!nestedTemplateExpanding && editableMasters.size() > 0) { 342 doc.putProperty(EDITING_TEMPLATE_DOC_PROPERTY, Boolean.TRUE); 343 344 if (doc instanceof BaseDocument) { 346 ((BaseDocument)doc).setPostModificationDocumentListener(this); 347 updateLastRegionBounds(); 348 } 349 350 componentOrigActionMap = CodeTemplateOverrideAction.installOverrideActionMap( 351 component, this); 352 353 EditorUI editorUI = Utilities.getEditorUI(component); 354 drawLayers = new ArrayList (editableMasters.size()); 355 for (Iterator it = editableMasters.iterator(); it.hasNext();) { 356 CodeTemplateParameterImpl paramImpl = paramImpl(((CodeTemplateParameter)it.next())); 357 CodeTemplateDrawLayer drawLayer = new CodeTemplateDrawLayer(paramImpl); 358 drawLayers.add(drawLayer); 359 editorUI.addLayer(drawLayer, CodeTemplateDrawLayer.VISIBILITY); 360 } 361 362 component.addKeyListener(this); 363 tabUpdate(); 364 365 } else { 366 forceCaretPosition(); 369 release(); 370 } 371 } 372 373 public void defaultKeyTypedAction(ActionEvent evt, Action origAction) { 374 origAction.actionPerformed(evt); 375 } 376 377 public void tabAction(ActionEvent evt, Action origAction) { 378 checkNotifyParameterUpdate(); 379 380 activeMasterIndex++; 381 activeMasterIndex %= editableMasters.size(); 382 tabUpdate(); 383 } 384 385 public void shiftTabAction(ActionEvent evt) { 386 checkNotifyParameterUpdate(); 387 388 if (activeMasterIndex-- == 0) 389 activeMasterIndex = editableMasters.size() - 1; 390 tabUpdate(); 391 } 392 393 public void enterAction(ActionEvent evt) { 394 checkNotifyParameterUpdate(); 395 396 activeMasterIndex++; 397 if (activeMasterIndex == editableMasters.size()) { 398 forceCaretPosition(); 399 release(); 400 } else { 401 tabUpdate(); 402 } 403 } 404 405 void undoAction(ActionEvent evt) { 406 } 408 409 void redoAction(ActionEvent evt) { 410 } 412 413 public String getDocParameterValue(CodeTemplateParameterImpl paramImpl) { 414 MutablePositionRegion positionRegion = paramImpl.getPositionRegion(); 415 int offset = positionRegion.getStartOffset(); 416 String parameterText; 417 try { 418 parameterText = doc.getText(offset, positionRegion.getEndOffset() - offset); 419 } catch (BadLocationException e) { 420 ErrorManager.getDefault().notify(e); 421 parameterText = null; 422 } 423 return parameterText; 424 } 425 426 public void setDocParameterValue(CodeTemplateParameterImpl paramImpl, String newValue) { 427 assert (paramImpl != getActiveMasterImpl()); assert (!paramImpl.isSlave()); SyncDocumentRegion region = paramImpl.getRegion(); 430 assert (region != null); 431 int offset = region.getFirstRegionStartOffset(); 432 int length = region.getFirstRegionLength(); 433 apiSetValueParamImpl = paramImpl; 434 try { 435 CharSequence parameterText = DocumentUtilities.getText(doc, offset, length); 436 if (!CharSequenceUtilities.textEquals(parameterText, newValue)) { 437 doc.remove(offset, length); 438 doc.insertString(offset, newValue, null); 439 region.sync(newValue.length()); 441 442 paramImpl.setValue(newValue, false); 443 } 444 } catch (BadLocationException e) { 445 ErrorManager.getDefault().notify(e); 446 } finally { 447 apiSetValueParamImpl = null; 448 } 449 } 450 451 public void insertUpdate(DocumentEvent evt) { 452 int offset = evt.getOffset(); 453 int insertLength = evt.getLength(); 454 if (offset + insertLength == caretPosition.getOffset()) { try { 456 caretPosition = doc.createPosition(offset); 457 } catch (BadLocationException e) { 458 ErrorManager.getDefault().notify(e); 459 } 460 } 461 462 if (!syncingDocModification) { 463 syncingDocModification = true; 464 syncInsert(evt); 465 syncingDocModification = false; 466 } 467 } 468 469 public void removeUpdate(DocumentEvent evt) { 470 if (!syncingDocModification) { 471 syncingDocModification = true; 472 syncRemove(evt); 473 syncingDocModification = false; 474 } 475 } 476 477 public void changedUpdate(DocumentEvent evt) { 478 } 479 480 public void keyPressed(KeyEvent e) { 481 if (KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0).equals(KeyStroke.getKeyStrokeForEvent(e))) { 482 release(); 483 e.consume(); 484 } else if (KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0).equals(KeyStroke.getKeyStrokeForEvent(e))) { 485 if (getActiveMaster() == null) { 486 checkNotifyParameterUpdate(); 487 release(); 488 } 489 } 490 } 491 492 public void keyReleased(KeyEvent e) { 493 } 494 495 public void keyTyped(KeyEvent e) { 496 } 497 498 private void forceCaretPosition() { 499 component.setCaretPosition(caretPosition.getOffset()); 500 } 501 502 private void notifyParameterUpdate(CodeTemplateParameter parameter, boolean typingChange) { 503 for (Iterator it = processors.iterator(); it.hasNext();) { 505 CodeTemplateProcessor processor = (CodeTemplateProcessor)it.next(); 506 processor.parameterValueChanged(parameter, typingChange); 507 } 508 } 509 510 private void checkNotifyParameterUpdate() { 511 if (activeMasterModified) { 512 activeMasterModified = false; 513 notifyParameterUpdate(getActiveMaster(), false); 514 } 515 } 516 517 private void parseParametrizedText() { 518 allParameters = new ArrayList (2); 519 allParametersUnmodifiable = Collections.unmodifiableList(allParameters); 520 masters = new ArrayList (2); 521 mastersUnmodifiable = Collections.unmodifiableList(masters); 522 editableMasters = new ArrayList (2); 523 parametrizedTextParser = new ParametrizedTextParser(this, parametrizedText); 524 parametrizedTextParser.parse(); 525 } 526 527 void notifyParameterParsed(CodeTemplateParameterImpl paramImpl) { 528 allParameters.add(paramImpl.getParameter()); 529 checkSlave(paramImpl.getParameter()); 530 } 531 532 533 540 private void checkSlave(CodeTemplateParameter parameter) { 541 for (Iterator it = getMasterParameters().iterator(); it.hasNext();) { 542 CodeTemplateParameter master = (CodeTemplateParameter)it.next(); 543 if (master.getName().equals(parameter.getName())) { 544 paramImpl(parameter).markSlave(master); 545 return; 546 } 547 } 548 masters.add(parameter); 550 if (parameter.isEditable()) { 551 editableMasters.add(parameter); 552 } 553 } 554 555 private static CodeTemplateParameterImpl paramImpl(CodeTemplateParameter param) { 556 return CodeTemplateSpiPackageAccessor.get().getImpl(param); 557 } 558 559 private void tabUpdate() { 560 updateLastRegionBounds(); 561 SyncDocumentRegion active = getActiveMasterImpl().getRegion(); 562 component.select(active.getFirstRegionStartOffset(), 563 active.getFirstRegionEndOffset()); 564 565 requestRepaint(); 567 } 568 569 private void requestRepaint() { 570 int startOffset = Integer.MAX_VALUE; 571 int endOffset = 0; 572 for (Iterator it = editableMasters.iterator(); it.hasNext();) { 573 SyncDocumentRegion region = paramImpl(((CodeTemplateParameter)it.next())).getRegion(); 574 startOffset = Math.min(startOffset, 575 region.getSortedRegion(0).getStartOffset()); 576 endOffset = Math.max(endOffset, region.getSortedRegion( 577 region.getRegionCount() - 1).getEndOffset()); 578 } 579 JTextComponent component = getComponent(); 580 if (endOffset != 0) { 581 component.getUI().damageRange(component, startOffset, endOffset); 582 } 583 } 584 585 private void release() { 586 synchronized (this) { 587 if (released) { 588 return; 589 } 590 this.released = true; 591 } 592 593 if (!nestedTemplateExpanding && editableMasters.size() > 0) { 594 if (doc instanceof BaseDocument) { 595 ((BaseDocument)doc).setPostModificationDocumentListener(null); 596 } 597 doc.putProperty(EDITING_TEMPLATE_DOC_PROPERTY, Boolean.FALSE); 598 599 component.removeKeyListener(this); 600 601 JTextComponent component = getComponent(); 603 component.setActionMap(componentOrigActionMap); 604 605 EditorUI editorUI = Utilities.getEditorUI(component); 607 if (editorUI != null) { 608 for (Iterator it = drawLayers.iterator(); it.hasNext();) { 609 DrawLayer drawLayer = (DrawLayer)it.next(); 610 editorUI.removeLayer(drawLayer.getName()); 611 } 612 } 613 component.putClientProperty(DrawLayer.TEXT_FRAME_START_POSITION_COMPONENT_PROPERTY, null); 614 component.putClientProperty(DrawLayer.TEXT_FRAME_END_POSITION_COMPONENT_PROPERTY, null); 615 616 requestRepaint(); 617 } 618 619 for (Iterator it = processors.iterator(); it.hasNext();) { 621 CodeTemplateProcessor processor = (CodeTemplateProcessor)it.next(); 622 processor.release(); 623 } 624 625 } 626 627 void syncInsert(DocumentEvent evt) { 628 int offset = evt.getOffset(); 630 int insertLength = evt.getLength(); 631 632 CodeTemplateParameterImpl activeMasterImpl = getActiveMasterImpl(); 633 if (apiSetValueParamImpl == null && activeMasterImpl != null 635 ) { 636 SyncDocumentRegion region = activeMasterImpl.getRegion(); 637 if (isManagedInsert(offset)) { 638 doc.putProperty("abbrev-ignore-modification", Boolean.TRUE); try { 640 region.sync((offset == lastActiveRegionStartOffset) ? insertLength : 0); 641 } finally { 642 doc.putProperty("abbrev-ignore-modification", Boolean.FALSE); } 644 activeMasterImpl.setValue(getDocParameterValue(activeMasterImpl), false); 645 activeMasterImpl.markUserModified(); 646 notifyParameterUpdate(activeMasterImpl.getParameter(), true); 647 } else { if (DocumentUtilities.isTypingModification(evt)) 649 release(); 650 } 651 } 652 updateLastRegionBounds(); 653 } 654 655 void syncRemove(DocumentEvent evt) { 656 CodeTemplateParameterImpl activeMasterImpl = getActiveMasterImpl(); 657 if (apiSetValueParamImpl == null && activeMasterImpl != null 659 ) { 660 SyncDocumentRegion region = activeMasterImpl.getRegion(); 661 if (isManagedRemove(evt.getOffset(), evt.getLength())) { 662 doc.putProperty("abbrev-ignore-modification", Boolean.TRUE); try { 664 region.sync(0); 665 } finally { 666 doc.putProperty("abbrev-ignore-modification", Boolean.FALSE); } 668 activeMasterImpl.setValue(getDocParameterValue(activeMasterImpl), false); 669 activeMasterImpl.markUserModified(); 670 if (doc.getProperty(BaseKit.DOC_REPLACE_SELECTION_PROPERTY) == null) 671 notifyParameterUpdate(activeMasterImpl.getParameter(), true); 672 } else { if (DocumentUtilities.isTypingModification(evt)) 674 release(); 675 } 676 } 677 updateLastRegionBounds(); 678 } 679 680 private void updateLastRegionBounds() { 681 CodeTemplateParameterImpl masterImpl = getActiveMasterImpl(); 682 if (masterImpl != null) { 683 SyncDocumentRegion region = masterImpl.getRegion(); 684 lastActiveRegionStartOffset = region.getFirstRegionStartOffset(); 685 lastActiveRegionEndOffset = region.getFirstRegionEndOffset(); 686 } else { 687 lastActiveRegionStartOffset = -1; 688 lastActiveRegionEndOffset = -1; 689 } 690 } 691 692 private boolean isManagedInsert(int offset) { 693 return (offset >= lastActiveRegionStartOffset 694 && offset <= lastActiveRegionEndOffset); 695 } 696 697 private boolean isManagedRemove(int offset, int length) { 698 return (offset >= lastActiveRegionStartOffset 699 && offset + length <= lastActiveRegionEndOffset); 700 } 701 702 private void addParameterRegion(List parameterRegions, CodeTemplateParameter parameter, 703 Document doc, int insertOffset) throws BadLocationException { 704 int startOffset = insertOffset + parameter.getInsertTextOffset(); 705 BaseDocument bdoc = (BaseDocument)doc; 706 Position startPos = bdoc.createPosition(startOffset); 707 Position endPos = doc.createPosition(startOffset + parameter.getValue().length()); 708 CodeTemplateParameterImpl paramImpl = CodeTemplateParameterImpl.get(parameter); 709 paramImpl.resetPositions(startPos, endPos); 710 parameterRegions.add(paramImpl.getPositionRegion()); 711 } 712 713 private String buildInsertText() { 714 return parametrizedTextParser.buildInsertText(allParameters); 715 } 716 717 } 718 | Popular Tags |