001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.acacia.client.widgets.code; 029 030import org.opencms.acacia.client.css.I_CmsLayoutBundle; 031import org.opencms.acacia.client.widgets.I_CmsEditWidget; 032import org.opencms.gwt.client.CmsCoreProvider; 033import org.opencms.gwt.client.Messages; 034import org.opencms.gwt.client.ui.FontOpenCms; 035import org.opencms.gwt.client.ui.input.CmsSelectBox; 036import org.opencms.gwt.client.ui.input.form.I_CmsHasPlaceholder; 037import org.opencms.gwt.client.util.CmsDomUtil; 038import org.opencms.gwt.client.util.CmsJsUtil; 039import org.opencms.gwt.shared.I_CmsCodeMirrorClientConfiguration; 040 041import java.util.Arrays; 042import java.util.HashMap; 043import java.util.LinkedHashMap; 044import java.util.Map; 045 046import com.google.gwt.core.client.JavaScriptObject; 047import com.google.gwt.dom.client.Document; 048import com.google.gwt.dom.client.Element; 049import com.google.gwt.event.dom.client.FocusEvent; 050import com.google.gwt.event.dom.client.FocusHandler; 051import com.google.gwt.event.logical.shared.HasResizeHandlers; 052import com.google.gwt.event.logical.shared.ResizeEvent; 053import com.google.gwt.event.logical.shared.ResizeHandler; 054import com.google.gwt.event.logical.shared.ValueChangeEvent; 055import com.google.gwt.event.logical.shared.ValueChangeHandler; 056import com.google.gwt.event.shared.HandlerRegistration; 057import com.google.gwt.json.client.JSONObject; 058import com.google.gwt.user.client.ui.ComplexPanel; 059import com.google.gwt.user.client.ui.FlowPanel; 060import com.google.web.bindery.autobean.shared.AutoBeanCodex; 061 062/** 063 * Client-side content editor widget for editing source code using the CodeMirror library. 064 */ 065public class CmsCodeMirrorWidget extends ComplexPanel 066implements I_CmsEditWidget, HasResizeHandlers, I_CmsHasPlaceholder { 067 068 /** Map from modes to labels. */ 069 private static LinkedHashMap<String, String> m_modes; 070 071 /** Map from simplified mode names to actual mode names (e.g. from 'javascript' to 'text/javascript'). */ 072 private static Map<String, String> m_simpleModeTranslations; 073 074 /** Helper for loading the necessary scripts. */ 075 private static final CmsCodeMirrorScriptLoader scriptLoader = new CmsCodeMirrorScriptLoader(); 076 077 static { 078 m_modes = new LinkedHashMap<>(); 079 m_simpleModeTranslations = new HashMap<>(); 080 addMode("text/css", "css", "CSS"); 081 addMode("text/html", "html", "HTML"); 082 addMode("text/plain", "text", "Text"); 083 addMode("text/x-java", "java", "Java"); 084 addMode("text/javascript", "javascript", "Javascript"); 085 addMode("application/x-jsp", "jsp", "JSP"); 086 addMode("application/xml", "xml", "XML"); 087 } 088 089 /** The token to control activation. */ 090 private boolean m_active = true; 091 092 /** The configuration from the server. */ 093 private I_CmsCodeMirrorClientConfiguration m_config; 094 095 /** The native CodeMirror instance. */ 096 private JavaScriptObject m_editor; 097 098 /** Boolean flag indicating whether syntax highlighting is currently turned on.*/ 099 private boolean m_highlighting = true; 100 101 private String m_id; 102 103 /** The currently selected mode (may be different from the actual mode in the CodeMirror instance). */ 104 private String m_mode = "text/plain"; 105 106 /** The mode selector. */ 107 private CmsSelectBox m_modeSelect; 108 109 /** The initial content (we need to save this because CodeMirror is loaded asynchronously). */ 110 private String m_originalContent; 111 112 private String m_placeholder = ""; 113 114 /** The previous value. */ 115 private String m_previousValue; 116 117 /** The 'redo' button. */ 118 private CmsCodeMirrorToolbarButton m_redo; 119 120 /** Toggle button for automatically closing brackets / tags. */ 121 private CmsCodeMirrorToggleButton m_toggleAutoClose; 122 123 /** The 'show tabs' toggle button. */ 124 private CmsCodeMirrorToggleButton m_toggleShowTabs; 125 126 /** The 'undo' button. */ 127 private CmsCodeMirrorToolbarButton m_undo; 128 129 /** 130 * Creates a new display widget.<p> 131 * 132 * @param config the widget configuration string 133 */ 134 public CmsCodeMirrorWidget(String config) { 135 136 super(); 137 m_id = "" + Math.random(); 138 setElement(Document.get().createDivElement()); 139 m_config = AutoBeanCodex.decode( 140 CmsCoreProvider.AUTO_BEAN_FACTORY, 141 I_CmsCodeMirrorClientConfiguration.class, 142 config).as(); 143 addStyleName("oc-codemirror-editorwidget"); 144 Integer height = m_config.getHeight(); 145 if (height != null) { 146 nativeSetProperty(getElement(), "--codemirror-height", height + "px"); 147 } 148 add(createToolbar(), getElement().<Element> cast()); 149 } 150 151 /** 152 * Helper method used to make a mode available for selection. 153 * 154 * @param name the normal MIME type name of the mode 155 * @param simpleName simple mode name for use in the server-side configuration, can be null 156 * @param title the title to be used in the mode selector 157 */ 158 private static void addMode(String name, String simpleName, String title) { 159 160 m_modes.put(name, title); 161 if (simpleName != null) { 162 m_simpleModeTranslations.put(simpleName, name); 163 } 164 } 165 166 /** 167 * @see com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.google.gwt.event.dom.client.FocusHandler) 168 */ 169 public HandlerRegistration addFocusHandler(FocusHandler handler) { 170 171 return addDomHandler(handler, FocusEvent.getType()); 172 } 173 174 /** 175 * @see com.google.gwt.event.logical.shared.HasResizeHandlers#addResizeHandler(com.google.gwt.event.logical.shared.ResizeHandler) 176 */ 177 178 public HandlerRegistration addResizeHandler(ResizeHandler handler) { 179 180 return addHandler(handler, ResizeEvent.getType()); 181 } 182 183 /** 184 * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) 185 */ 186 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) { 187 188 return addHandler(handler, ValueChangeEvent.getType()); 189 } 190 191 /** 192 * Represents a value change event.<p> 193 * 194 */ 195 public void fireChangeEvent() { 196 197 updateUndoRedo(); 198 fireValueChange(false); 199 } 200 201 /** 202 * Represents a resize event.<p> 203 * @param event from text area panel 204 */ 205 public void fireResizeeEvent(ResizeEvent event) { 206 207 ResizeEvent.fire(this, event.getWidth(), event.getHeight()); 208 } 209 210 /** 211 * Gets the effective mode to use, which depends both on the selected mode and on whether the user has enabled syntax highlighting. 212 * 213 * @return the effective mode 214 */ 215 public String getEffectiveMode() { 216 217 return m_highlighting ? m_mode : "text/plain"; 218 } 219 220 /** 221 * @see com.google.gwt.user.client.ui.HasValue#getValue() 222 */ 223 public String getValue() { 224 225 return getContent(); 226 } 227 228 /** 229 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#isActive() 230 */ 231 public boolean isActive() { 232 233 return m_active; 234 } 235 236 /** 237 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#onAttachWidget() 238 */ 239 public void onAttachWidget() { 240 241 onAttach(); 242 } 243 244 /** 245 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#owns(com.google.gwt.dom.client.Element) 246 */ 247 public boolean owns(Element element) { 248 249 return getElement().isOrHasChild(element); 250 251 } 252 253 /** 254 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setActive(boolean) 255 */ 256 public void setActive(boolean active) { 257 258 if (m_active == active) { 259 return; 260 } 261 m_active = active; 262 if (m_editor != null) { 263 if (m_active) { 264 getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.form().inActive()); 265 fireValueChange(true); 266 } else { 267 getElement().addClassName(I_CmsLayoutBundle.INSTANCE.form().inActive()); 268 } 269 } 270 } 271 272 /** 273 * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setName(java.lang.String) 274 */ 275 public void setName(String name) { 276 277 // not used 278 } 279 280 /** 281 * @see org.opencms.gwt.client.ui.input.form.I_CmsHasPlaceholder#setPlaceholder(java.lang.String) 282 */ 283 @Override 284 public void setPlaceholder(String placeholder) { 285 286 if (placeholder == null) { 287 placeholder = ""; 288 } 289 m_placeholder = placeholder; 290 291 } 292 293 /** 294 * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object) 295 */ 296 public void setValue(String value) { 297 298 setValue(value, false); 299 } 300 301 /** 302 * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean) 303 */ 304 public void setValue(String value, boolean fireEvents) { 305 306 if (value != null) { 307 value = value.trim(); 308 } 309 setPreviousValue(value); 310 if (m_editor == null) { 311 // editor has not been initialized yet 312 m_originalContent = value; 313 } else { 314 nativeSetContent(value); 315 } 316 if (fireEvents) { 317 fireValueChange(true); 318 } 319 320 } 321 322 /** 323 * Fires the value change event, if the value has changed.<p> 324 * 325 * @param force <code>true</code> to force firing the event, not regarding an actually changed value 326 */ 327 protected void fireValueChange(boolean force) { 328 329 String currentValue = getValue(); 330 if (force || !currentValue.equals(m_previousValue)) { 331 m_previousValue = currentValue; 332 ValueChangeEvent.fire(this, currentValue); 333 } 334 } 335 336 /** 337 * Returns the previous value.<p> 338 * 339 * @return the previous value 340 */ 341 protected String getPreviousValue() { 342 343 return m_previousValue; 344 } 345 346 /** 347 * @see com.google.gwt.user.client.ui.FocusWidget#onAttach() 348 */ 349 @Override 350 protected void onAttach() { 351 352 super.onAttach(); 353 scriptLoader.load(() -> { 354 int height = -1; 355 if (m_config.getHeight() != null) { 356 height = m_config.getHeight().intValue(); 357 } 358 initCodeMirror( 359 CmsCodeMirrorWidget.this, 360 getElement(), 361 CmsJsUtil.parseJSON(m_config.getPhrasesJSON()), 362 height, 363 m_placeholder); 364 initializeUserControlledSettings(); 365 if (m_originalContent != null) { 366 nativeSetContent(m_originalContent); 367 clearHistory(); 368 updateUndoRedo(); 369 } 370 }); 371 } 372 373 /** 374 * Sets the previous value.<p> 375 * 376 * @param previousValue the previous value to set 377 */ 378 protected void setPreviousValue(String previousValue) { 379 380 m_previousValue = previousValue; 381 } 382 383 /** 384 * Clears the undo / redo history. 385 */ 386 private native void clearHistory() /*-{ 387 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 388 editor.clearHistory(); 389 }-*/; 390 391 /** 392 * Creates a simple toggle button for enabling / disabling a native CodeMirror option. 393 * 394 * @param icon the icon 395 * @param optionName the option name 396 * @param initialValue the initial vale of the option 397 * 398 * @return the newly created toggle button 399 */ 400 private CmsCodeMirrorToggleButton createOptionButton(FontOpenCms icon, String optionName, boolean initialValue) { 401 402 CmsCodeMirrorToggleButton result = new CmsCodeMirrorToggleButton(icon); 403 result.setValue(initialValue, false); 404 result.addValueChangeHandler(event -> nativeSetOption(optionName, event.getValue().booleanValue())); 405 return result; 406 407 } 408 409 /** 410 * Creates the editor tool bar. 411 * @return the editor tool bar 412 */ 413 private FlowPanel createToolbar() { 414 415 FlowPanel result = new FlowPanel(); 416 result.addStyleName("oc-codewidget-toolbar"); 417 418 { 419 CmsCodeMirrorToolbarButton undo = new CmsCodeMirrorToolbarButton(FontOpenCms.UNDO); 420 undo.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_BUTTON_UNDO_0)); 421 undo.addClickHandler(event -> executeCommand("undo")); 422 m_undo = undo; 423 result.add(undo); 424 } 425 426 { 427 CmsCodeMirrorToolbarButton redo = new CmsCodeMirrorToolbarButton(FontOpenCms.REDO); 428 redo.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_BUTTON_REDO_0)); 429 redo.addClickHandler(event -> executeCommand("redo")); 430 m_redo = redo; 431 result.add(redo); 432 } 433 434 { 435 CmsCodeMirrorToolbarButton search = new CmsCodeMirrorToolbarButton(FontOpenCms.SEARCH); 436 search.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_BUTTON_FIND_0)); 437 search.addClickHandler(event -> { 438 search(); 439 }); 440 result.add(search); 441 } 442 443 { 444 CmsCodeMirrorToolbarButton searchReplace = new CmsCodeMirrorToolbarButton(FontOpenCms.SEARCH_REPLACE); 445 searchReplace.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_BUTTON_REPLACE_0)); 446 searchReplace.addClickHandler(event -> searchReplace()); 447 result.add(searchReplace); 448 } 449 450 { 451 CmsCodeMirrorToolbarButton lineButton = new CmsCodeMirrorToolbarButton("L"); 452 lineButton.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_BUTTON_JUMP_TO_LINE_0)); 453 lineButton.addClickHandler(event -> { 454 executeCommand("jumpToLine"); 455 }); 456 result.add(lineButton); 457 } 458 { 459 CmsCodeMirrorToggleButton highlight = new CmsCodeMirrorToggleButton(FontOpenCms.HIGHLIGHT); 460 highlight.setValue(true, false); 461 highlight.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_TOGGLE_SYNTAX_HIGHLIGHTING_0)); 462 highlight.addValueChangeHandler(event -> setSyntaxHighlightingEnabled(event.getValue().booleanValue())); 463 result.add(highlight); 464 } 465 466 { 467 CmsCodeMirrorToggleButton wrap = createOptionButton(FontOpenCms.WRAP_LINES, "lineWrapping", false); 468 wrap.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_TOGGLE_LINE_WRAP_0)); 469 result.add(wrap); 470 } 471 472 { 473 CmsCodeMirrorToggleButton showTabs = new CmsCodeMirrorToggleButton(FontOpenCms.INVISIBLE_CHARS); 474 showTabs.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_TOGGLE_TABS_0)); 475 showTabs.addValueChangeHandler(event -> setShowTabs(event.getValue().booleanValue())); 476 m_toggleShowTabs = showTabs; 477 result.add(showTabs); 478 } 479 480 { 481 CmsCodeMirrorToggleButton brackets = new CmsCodeMirrorToggleButton(FontOpenCms.BRACKETS); 482 brackets.setTitle(Messages.get().key(Messages.GUI_CODEMIRROR_TOGGLE_AUTO_CLOSE_BRACKETS_0)); 483 brackets.addValueChangeHandler(event -> setAutoCloseBrackets(event.getValue().booleanValue())); 484 m_toggleAutoClose = brackets; 485 result.add(brackets); 486 } 487 488 m_modeSelect = new CmsSelectBox(m_modes); 489 m_modeSelect.addStyleName("oc-codewidget-mode-select"); 490 491 Map<String, String> items = new LinkedHashMap<>(); 492 for (String fontSize : Arrays.asList("8px", "10px", "12px", "14px", "16px", "18px", "20px")) { 493 items.put(fontSize, fontSize); 494 } 495 CmsSelectBox select = new CmsSelectBox(items); 496 select.addStyleName("oc-codewidget-fontsize-select"); 497 select.setFormValue("14px"); 498 result.add(select); 499 result.add(m_modeSelect); 500 501 select.addValueChangeHandler(event -> { 502 String fontSize = event.getValue(); 503 setFontSize(fontSize); 504 }); 505 506 m_modeSelect.addValueChangeHandler(event -> { 507 String mode = event.getValue(); 508 setMode(mode); 509 510 }); 511 return result; 512 513 } 514 515 /** 516 * Executes a named CodeMirror command. 517 * 518 * @param command the name of the command to execute 519 */ 520 private native void executeCommand(String command) /*-{ 521 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 522 $wnd.CodeMirror.commands[command](editor); 523 }-*/; 524 525 /** 526 * Gets the editor content. 527 * 528 * @return the editor content 529 */ 530 private native String getContent() /*-{ 531 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 532 return editor.getValue(); 533 }-*/; 534 535 /** 536 * Gets the history size. 537 * 538 * @return the history size 539 */ 540 private native JavaScriptObject getHistorySize() /*-{ 541 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 542 return editor.historySize(); 543 }-*/; 544 545 /** 546 * Checks if a CodeMirror dialog is currently being displayed. 547 * 548 * @return true if a CodeMirror dialog is being displayed 549 */ 550 private boolean hasDialog() { 551 552 return CmsDomUtil.getElementsByClass("CodeMirror-dialog", getElement()).size() > 0; 553 } 554 555 /** 556 * Initializes the CodeMirror instance. 557 * 558 * @param elem the parent element 559 * @param phrases the localization phrases 560 * @param height the initial editor height, or -1 if no height should be set 561 */ 562 private native void initCodeMirror( 563 CmsCodeMirrorWidget instance, 564 Element elem, 565 JavaScriptObject phrases, 566 int height, 567 String placeholder) /*-{ 568 var config = { 569 theme: "eclipse", 570 mode: "text/plain", 571 lineNumbers: true, 572 styleActiveLine: true, 573 fixedGutter: true, 574 indentUnit: 4, 575 indentWithTabs: true, 576 smartIndent: false, 577 placeholder: placeholder 578 }; 579 580 config.phrases = phrases; 581 var result = $wnd.CodeMirror(elem, config); 582 instance.getEditor = function() { 583 return result; 584 } 585 this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor = result; 586 var that = this; 587 result.on("changes", function(editor, changes) { 588 that.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::fireChangeEvent()(); 589 }); 590 if (height > 0) { 591 result.setSize(null, height); 592 } 593 var resizeHandler = new ResizeObserver(function(entries) { 594 result.refresh(); 595 }); 596 resizeHandler.observe(elem); 597 }-*/; 598 599 /** 600 * Initializes the settings controlled by buttons / select boxes after CodeMirror is loaded. 601 */ 602 private void initializeUserControlledSettings() { 603 604 String startMode = m_config.getStartMode(); 605 if (startMode != null) { 606 if (!m_modes.containsKey(startMode)) { 607 startMode = m_simpleModeTranslations.get(startMode.toLowerCase()); 608 } 609 if (startMode != null) { 610 m_modeSelect.setFormValue(startMode, true); 611 } 612 } 613 m_toggleAutoClose.setValue(true, /*fireEvents=*/true); 614 m_toggleShowTabs.setValue(false, /*fireEvents=*/true); 615 } 616 617 /** 618 * Sets the content in the native CodeMirror instance. 619 * 620 * @param content the new editor content 621 */ 622 private native void nativeSetContent(String content) /*-{ 623 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 624 editor.setValue(content); 625 }-*/; 626 627 /** 628 * Sets the mode on the native CodeMirror instance. 629 * 630 * @param mode the mode to set 631 */ 632 private native void nativeSetMode(String mode) /*-{ 633 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 634 editor.setOption("mode", mode); 635 }-*/; 636 637 /** 638 * Sets a CodeMirror option of type 'boolean'. 639 * 640 * @param name the name of the option 641 * @param booleanValue the option value 642 */ 643 private native void nativeSetOption(String name, boolean booleanValue) /*-{ 644 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 645 if (editor) { // editor may not be fully loaded yet 646 editor.setOption(name, booleanValue); 647 } 648 }-*/; 649 650 /** 651 * Sets a custom CSS property on the given element. 652 * 653 * @param element the element on which to set the property 654 * @param name the name of the CSS property 655 * @param value the value of the CSS property 656 */ 657 private native void nativeSetProperty(com.google.gwt.dom.client.Element element, String name, String value) /*-{ 658 element.style.setProperty(name, value); 659 }-*/; 660 661 /** 662 * Executes the 'redo' command. 663 */ 664 private void redo() { 665 666 executeCommand("redo"); 667 } 668 669 /** 670 * Refreshes the editor layout. 671 */ 672 private native void refresh() /*-{ 673 var editor = this.@org.opencms.acacia.client.widgets.code.CmsCodeMirrorWidget::m_editor; 674 editor.refresh(); 675 }-*/; 676 677 /** 678 * Starts a search operation. 679 */ 680 private void search() { 681 682 if (!hasDialog()) { 683 // executing a dialog command while a dialog is active leads to weird/confusing UI state 684 executeCommand("find"); 685 } 686 } 687 688 /** 689 * Starts a search/replace operation. 690 */ 691 private void searchReplace() { 692 693 if (!hasDialog()) { 694 // executing a dialog command while a dialog is active leads to weird/confusing UI state 695 executeCommand("replace"); 696 } 697 } 698 699 /** 700 * Enables/disables automatic closing of brackets. 701 * 702 * @param value true if automatic bracket closing should be enabled 703 */ 704 private void setAutoCloseBrackets(boolean value) { 705 706 nativeSetOption("autoCloseBrackets", value); 707 nativeSetOption("autoCloseTags", value); 708 nativeSetOption("matchBrackets", value); 709 } 710 711 /** 712 * Updates the editor font size. 713 * 714 * @param value the font size, e.g. '12px' 715 */ 716 private void setFontSize(String value) { 717 718 nativeSetProperty(getElement().cast(), "--codemirror-font-size", value); 719 refresh(); 720 } 721 722 /** 723 * Sets the mode selected by the user (this may not result in the mode actually being changed in the editor if syntax highlighting is turned off). 724 * 725 * @param mode the new mode 726 */ 727 private void setMode(String mode) { 728 729 m_mode = mode; 730 if (m_editor != null) { 731 nativeSetMode(getEffectiveMode()); 732 } 733 734 } 735 736 /** 737 * Enables/disables tab visibility. 738 * 739 * @param value true if tabs should be shown 740 */ 741 private void setShowTabs(boolean value) { 742 743 nativeSetProperty(getElement(), "--codemirror-tab-state", value ? "visible" : "hidden"); 744 nativeSetOption("showTrailingSpace", value); 745 746 } 747 748 /** 749 * Enables / disables syntax highlighting. 750 * 751 * @param booleanValue true if syntax highlighting should be enabled 752 */ 753 private void setSyntaxHighlightingEnabled(boolean booleanValue) { 754 755 m_highlighting = booleanValue; 756 nativeSetMode(getEffectiveMode()); 757 } 758 759 /** 760 * Updates undo/redo button states based on history size. 761 */ 762 private void updateUndoRedo() { 763 764 JavaScriptObject jso = getHistorySize(); 765 JSONObject historySize = new JSONObject(jso); 766 int undo = (int)(historySize.get("undo").isNumber().doubleValue()); 767 int redo = (int)(historySize.get("redo").isNumber().doubleValue()); 768 m_undo.setEnabled(undo > 0); 769 m_redo.setEnabled(redo > 0); 770 } 771 772}