/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.smart.dataui; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.dnd.DropTarget; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.InputEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JEditorPane; import javax.swing.TransferHandler; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.plaf.ComponentUI; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.EditorKit; import javax.swing.text.View; import javax.swing.text.html.HTMLFrameHyperlinkEvent; import com.servoy.base.util.ITagResolver; import com.servoy.j2db.ExitScriptException; import com.servoy.j2db.FormController; import com.servoy.j2db.IApplication; import com.servoy.j2db.IModeManager; import com.servoy.j2db.IScriptExecuter; import com.servoy.j2db.IServiceProvider; import com.servoy.j2db.component.ComponentFactory; import com.servoy.j2db.component.ISupportAsyncLoading; import com.servoy.j2db.dataprocessing.IDisplayData; import com.servoy.j2db.dataprocessing.IEditListener; import com.servoy.j2db.dnd.FormDataTransferHandler; import com.servoy.j2db.dnd.ISupportDragNDropTextTransfer; import com.servoy.j2db.printing.IFixedPreferredWidth; import com.servoy.j2db.smart.MainPanel; import com.servoy.j2db.smart.SwingForm; import com.servoy.j2db.smart.SwingRuntimeWindow; import com.servoy.j2db.smart.TextToolbar; import com.servoy.j2db.ui.IDataRenderer; import com.servoy.j2db.ui.IDisplayTagText; import com.servoy.j2db.ui.IEditProvider; import com.servoy.j2db.ui.IEventExecutor; import com.servoy.j2db.ui.IFieldComponent; import com.servoy.j2db.ui.ILabel; import com.servoy.j2db.ui.IScrollPane; import com.servoy.j2db.ui.ISupportCachedLocationAndSize; import com.servoy.j2db.ui.ISupportEditProvider; import com.servoy.j2db.ui.ISupportOnRender; import com.servoy.j2db.ui.scripting.AbstractRuntimeField; import com.servoy.j2db.ui.scripting.AbstractRuntimeTextEditor; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.EnableScrollPanel; import com.servoy.j2db.util.HtmlUtils; import com.servoy.j2db.util.ISkinnable; import com.servoy.j2db.util.ISupplyFocusChildren; import com.servoy.j2db.util.ScopesUtils; import com.servoy.j2db.util.Text; import com.servoy.j2db.util.Utils; import com.servoy.j2db.util.gui.FixedHTMLEditorKit; import com.servoy.j2db.util.gui.FixedJEditorPane; import com.servoy.j2db.util.rtf.FixedRTFEditorKit; /** * A rich text editor component * * @author jblok */ public class DataTextEditor extends EnableScrollPanel implements IDisplayData, IDisplayTagText, IFieldComponent, IScrollPane, ISupportAsyncLoading, IFixedPreferredWidth, ISupportCachedLocationAndSize, ISupplyFocusChildren, ISupportDragNDropTextTransfer, ISupportEditProvider, ISupportOnRender { private final FixedJEditorPane enclosedComponent; private String dataProviderID; private String tagText; private EditorKit editorKit; private final EditorKit plainEditorKit; private Document editorDocument; private final Document plainEditorDocument; private final IApplication application; private final EventExecutor eventExecutor; private MouseAdapter rightclickMouseAdapter = null; private SwingRuntimeWindow parentWindow; private final AbstractRuntimeTextEditor<IFieldComponent, JEditorPane> scriptable; public DataTextEditor(IApplication app, AbstractRuntimeTextEditor<IFieldComponent, JEditorPane> scriptable, int type) { super(); application = app; getViewport().setView(new MyEditorPane(app.getScheduledExecutor())); enclosedComponent = (FixedJEditorPane)getViewport().getView(); eventExecutor = new EventExecutor(this, enclosedComponent) { @Override public void fireLeaveCommands(Object display, boolean focusEvent, int modifiers) { if (hasLeaveCmds()) { editProvider.focusLost(new FocusEvent(DataTextEditor.this, FocusEvent.FOCUS_LOST)); } super.fireLeaveCommands(display, focusEvent, modifiers); } }; enclosedComponent.addKeyListener(eventExecutor); this.scriptable = scriptable; scriptable.setTextComponent(enclosedComponent); plainEditorKit = enclosedComponent.getEditorKit(); plainEditorDocument = getDocument(); // setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); // setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); // setContentType("text/rtf" ); if (type == ComponentFactory.RTF_AREA) { editorKit = new FixedRTFEditorKit(); } else { editorKit = new FixedHTMLEditorKit(app); enclosedComponent.setDocument(editorKit.createDefaultDocument()); } enclosedComponent.setBorder(BorderFactory.createEmptyBorder()); enclosedComponent.setEditorKit(editorKit); editorDocument = getDocument(); prepareForTextToolbarHandling(); enclosedComponent.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE && !Utils.equalObjects(previousValue, getValueObject())) { e.consume(); } } @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE && previousValue != null && !Utils.equalObjects(previousValue, getValueObject())) { StringReader sr = new StringReader((String)previousValue); try { enclosedComponent.getDocument().remove(0, enclosedComponent.getDocument().getLength()); editorKit.read(sr, enclosedComponent.getDocument(), 0); } catch (Exception ex) { Debug.error(ex); } finally { e.consume(); } } } }); // Action[] actions = enclosedComponent.getActions(); // for (int i = 0; i < actions.length; i++) // { // Debug.trace("Found action "+actions[i]+" name "+actions[i].getValue(Action.NAME)); // } } public final AbstractRuntimeField getScriptObject() { return scriptable; } private void prepareForTextToolbarHandling() { addFocusListener(new FocusListener() { public void focusLost(FocusEvent e) { setTextToolBarComponent(null); } public void focusGained(FocusEvent e) { if (eventExecutor.getValidationEnabled()) { setTextToolBarComponent(enclosedComponent); } // else find mode... } }); addHierarchyListener(new HierarchyListener() { public void hierarchyChanged(HierarchyEvent e) { if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == HierarchyEvent.PARENT_CHANGED) { // get the new text toolbar to be used Container parent = getParent(); while (parent != null && !(parent instanceof MainPanel)) { parent = parent.getParent(); } if (parent instanceof MainPanel) { parentWindow = (SwingRuntimeWindow)application.getRuntimeWindowManager().getWindow(((MainPanel)parent).getContainerName()); } else { parentWindow = null; } } } }); } private void setTextToolBarComponent(Component comp) { TextToolbar textToolbar = parentWindow != null ? parentWindow.getTextToolbar() : null; if (textToolbar != null) { if (comp instanceof JEditorPane && comp.isEnabled()) { textToolbar.setTextComponent((JEditorPane)comp); textToolbar.setEnabled(true); textToolbar.setVisible(true); } else { textToolbar.setTextComponent(null); textToolbar.setEnabled(false); textToolbar.setVisible(true); } } } /* * _____________________________________________________________ Methods for event handling */ public void addScriptExecuter(IScriptExecuter el) { eventExecutor.setScriptExecuter(el); } public IEventExecutor getEventExecutor() { return eventExecutor; } public void setEnterCmds(String[] ids, Object[][] args) { eventExecutor.setEnterCmds(ids, args); } public void setLeaveCmds(String[] ids, Object[][] args) { eventExecutor.setLeaveCmds(ids, args); } public boolean isValueValid() { return isValueValid; } private boolean isValueValid = true; private Object previousValidValue; public void setValueValid(boolean valid, Object oldVal) { application.getRuntimeProperties().put(IServiceProvider.RT_LASTFIELDVALIDATIONFAILED_FLAG, Boolean.valueOf(!valid)); isValueValid = valid; if (!isValueValid) { previousValidValue = oldVal; application.invokeLater(new Runnable() { public void run() { requestFocus(); } }); } else { previousValidValue = null; } } public void notifyLastNewValueWasChange(Object oldVal, Object newVal) { if (previousValidValue != null) { oldVal = previousValidValue; } previousValue = (newVal == null ? null : newVal.toString()); eventExecutor.fireChangeCommand(oldVal, newVal, false, this); // if change cmd is not succeeded also don't call action cmd? if (isValueValid) { eventExecutor.fireActionCommand(false, this); } } public void setChangeCmd(String id, Object[] args) { eventExecutor.setChangeCmd(id, args); } public void setActionCmd(String id, Object[] args) { eventExecutor.setActionCmd(id, args); } public void setRightClickCommand(String rightClickCmd, Object[] args) { eventExecutor.setRightClickCmd(rightClickCmd, args); if (rightClickCmd != null && rightclickMouseAdapter == null) { rightclickMouseAdapter = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) handle(e); } @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) handle(e); } private void handle(MouseEvent e) { if (isEnabled()) { eventExecutor.fireRightclickCommand(true, DataTextEditor.this, e.getModifiers(), e.getPoint()); } } }; enclosedComponent.addMouseListener(rightclickMouseAdapter); addMouseListener(rightclickMouseAdapter); } } private boolean wasEditable; public void setValidationEnabled(boolean b) { if (eventExecutor.getValidationEnabled() == b) return; if (dataProviderID != null && ScopesUtils.isVariableScope(dataProviderID)) return; eventExecutor.setValidationEnabled(b); boolean prevEditState = editState; if (b) { setEditable(wasEditable); enclosedComponent.setEditorKit(editorKit); enclosedComponent.setDocument(editorDocument); previousValue = getValueObject(); try { plainEditorDocument.remove(0, plainEditorDocument.getLength()); } catch (BadLocationException e) { Debug.error(e); } } else { wasEditable = enclosedComponent.isEditable(); if (!Boolean.TRUE.equals(application.getClientProperty(IApplication.LEAVE_FIELDS_READONLY_IN_FIND_MODE))) { setEditable(true); } enclosedComponent.setEditorKit(plainEditorKit); enclosedComponent.setDocument(plainEditorDocument); } editState = prevEditState; } public void setSelectOnEnter(boolean b) { eventExecutor.setSelectOnEnter(b); } //_____________________________________________________________ @Override public void setFont(Font f) { if (enclosedComponent != null) { enclosedComponent.setFont(f); } } @Override public Font getFont() { if (enclosedComponent != null) { return enclosedComponent.getFont(); } return super.getFont(); } public void setHorizontalAlignment(int a) { //ignore } public void setMaxLength(int i) { //ignore } @Override public void setName(String name) { super.setName(name); enclosedComponent.setName(name); } public void setMargin(Insets m) { // enclosedComponent.setMargin(i); seems to have no effect enclosedComponent.setBorder(BorderFactory.createCompoundBorder(enclosedComponent.getBorder(), BorderFactory.createEmptyBorder(m.top, m.left, m.bottom, m.right))); } public Insets getMargin() { return null; } public Document getDocument() { return enclosedComponent.getDocument(); } public Component[] getFocusChildren() { return new Component[] { enclosedComponent }; } private class MyEditorPane extends FixedJEditorPane implements ISkinnable, ISupportDragNDropTextTransfer { MyEditorPane(Executor exe) { super(exe); setDragEnabledEx(true); // getDocument().putProperty("IgnoreCharsetDirective", Boolean.valueOf(true)); /* * Key for a client property used to indicate whether the default font and foreground color from the component are used if a font or foreground * color is not specified in the styled text */ putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); } private void setDragEnabledEx(boolean b) { try { Method m = getClass().getMethod("setDragEnabled", new Class[] { boolean.class }); //$NON-NLS-1$ m.invoke(this, new Object[] { new Boolean(b) }); } catch (Exception e) { // Debug.trace(e);//is intenionaly trace, becouse fails before 1.4 } } /** * Fix for bad font rendering (bad kerning == strange spacing) in java 1.5 see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5097047 */ @Override public FontMetrics getFontMetrics(Font font) { if (application != null)//getFontMetrics can be called in the constructor super call before application is assigned { boolean isPrinting = Utils.getAsBoolean(application.getRuntimeProperties().get("isPrinting")); //$NON-NLS-1$ if (isPrinting) { Graphics g = (Graphics)application.getRuntimeProperties().get("printGraphics"); //$NON-NLS-1$ if (g != null) { return g.getFontMetrics(font); } } } return super.getFontMetrics(font); } // MAC FIX @Override public Insets getInsets() { Insets insets = super.getInsets(); if (insets == null) { insets = new Insets(0, 0, 0, 0); } return insets; } // MAC FIX @Override public Insets getMargin() { Insets insets = super.getMargin(); if (insets == null) { insets = new Insets(0, 0, 0, 0); } return insets; } @Override public void transferFocus() { DataTextEditor.this.transferFocus(); } @Override public void setUI(ComponentUI ui) { super.setUI(ui); } @Override public void setText(String t) { super.setText(t); setCaretPosition(0); } //method to overcome exception by sun system thrown during printing @Override public Dimension getPreferredSize() { try { return super.getPreferredSize(); } catch (RuntimeException ex) { Debug.error("Invalid HTML for " + dataProviderID + " html: " + previousValue + "\n" + getText()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //throw new IllegalArgumentException("Invalid HTML for "+ dataProviderID); } return getSize(); } //method to overcome exception by sun system thrown during printing @Override public boolean getScrollableTracksViewportWidth() { try { return (application.getModeManager().getMode() == IModeManager.PREVIEW_MODE) ? true : super.getScrollableTracksViewportWidth(); } catch (RuntimeException ex) { Debug.error("Invalid HTML for " + dataProviderID + " html: " + previousValue + "\n" + getText()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //throw new IllegalArgumentException("Invalid HTML for "+ dataProviderID); } return false; } //method to overcome exception by sun system thrown during printing @Override public boolean getScrollableTracksViewportHeight() { try { return (application.getModeManager().getMode() == IModeManager.PREVIEW_MODE) ? true : super.getScrollableTracksViewportHeight(); } catch (RuntimeException ex) { Debug.error("Invalid HTML for " + dataProviderID + " html: " + previousValue + "\n" + getText()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ // throw new IllegalArgumentException("Invalid HTML for "+ dataProviderID); } return false; } //fix for incorrect clipping @Override public void print(Graphics g) { Shape saveClip = g.getClip(); try { int w = DataTextEditor.this.getWidth(); w -= DataTextEditor.this.getInsets().left + DataTextEditor.this.getInsets().right; int h = DataTextEditor.this.getHeight(); h -= DataTextEditor.this.getInsets().top + DataTextEditor.this.getInsets().bottom; if (saveClip != null) { g.setClip(saveClip.getBounds().intersection(new Rectangle(0, 0, w, h))); } else { g.setClip(0, 0, w, h); } super.print(g); } finally { g.setClip(saveClip); } } @Override public void paint(Graphics g) { try { super.paint(g); } catch (RuntimeException re) { Debug.error("Error in painting HTML/RTF Area, check your html: " + getText(), re); //$NON-NLS-1$ } //if you want to see all the view enable the following code DO NOT DELETE if (false) { View v = this.getUI().getRootView(this); walkView(g, v, getBounds()); } } // Recursively walks a view hierarchy private void walkView(Graphics g, View view, Rectangle allocation) { // Get number of children views int n = view.getViewCount(); // Visit the children of this view for (int i = 0; i < n; i++) { View kid = view.getView(i); java.awt.Shape kidshape = view.getChildAllocation(i, allocation); if (kidshape == null) continue; Rectangle kidbox = kidshape.getBounds(); g.drawRect(kidbox.x, kidbox.y, kidbox.width, kidbox.height); walkView(g, kid, kidbox); } } @Override public void copy() { if (textTransferHandler != null) { Action copyAction = FormDataTransferHandler.getCopyFormDataAction(); copyAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, (String)copyAction.getValue(Action.NAME), EventQueue.getMostRecentEventTime(), getCurrentEventModifiers())); } else super.copy(); } @Override public void cut() { if (textTransferHandler != null && isEditable() && isEnabled()) { Action cutAction = FormDataTransferHandler.getCutFormDataAction(); cutAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, (String)cutAction.getValue(Action.NAME), EventQueue.getMostRecentEventTime(), getCurrentEventModifiers())); } else super.cut(); } @Override public void paste() { if (textTransferHandler != null && isEditable() && isEnabled()) { Action pasteAction = FormDataTransferHandler.getPasteFormDataAction(); pasteAction.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, (String)pasteAction.getValue(Action.NAME), EventQueue.getMostRecentEventTime(), getCurrentEventModifiers())); } else super.paste(); } private int getCurrentEventModifiers() { int modifiers = 0; AWTEvent currentEvent = EventQueue.getCurrentEvent(); if (currentEvent instanceof InputEvent) { modifiers = ((InputEvent)currentEvent).getModifiers(); } else if (currentEvent instanceof ActionEvent) { modifiers = ((ActionEvent)currentEvent).getModifiers(); } return modifiers; } private TransferHandler textTransferHandler; /* * @see com.servoy.j2db.dnd.ISupportDragNDropTextTransfer#clearTransferHandler() */ public void clearTransferHandler() { textTransferHandler = getTransferHandler(); setTransferHandler(null); } /* * @see com.servoy.j2db.dnd.ISupportDragNDropTextTransfer#getTextTransferHandler() */ public TransferHandler getTextTransferHandler() { return textTransferHandler; } } public JEditorPane getRealEditor() { return enclosedComponent; } public boolean needEditListener() { return true; } private EditProvider editProvider = null; public IEditProvider getEditProvider() { return editProvider; } public void addEditListener(IEditListener l) { if (editProvider == null) { editProvider = new EditProvider(this, true); addFocusListener(editProvider); editorDocument.addDocumentListener(editProvider); plainEditorDocument.addDocumentListener(editProvider); enclosedComponent.addPropertyChangeListener("document", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (Utils.stringSafeEquals("document", evt.getPropertyName())) //$NON-NLS-1$ { editorDocument.removeDocumentListener(editProvider); editorDocument = (Document)evt.getNewValue(); editProvider.resetState(); editorDocument.addDocumentListener(editProvider); } } }); // addPropertyChangeListener("text", editProvider); editProvider.addEditListener(l); editProvider.setEditable(enclosedComponent.isEditable()); try { DropTarget dt = enclosedComponent.getDropTarget(); if (dt != null) dt.addDropTargetListener(editProvider); } catch (Exception e) { Debug.error(e); } } } public void setEditable(boolean b) { editState = b; enclosedComponent.setEditable(b); if (!b && editorKit instanceof FixedHTMLEditorKit && linkListener == null) { linkListener = new LinkListener(); enclosedComponent.addHyperlinkListener(linkListener); } if (editProvider != null) editProvider.setEditable(b); enclosedComponent.removeMouseListener(eventExecutor); if (!b) { enclosedComponent.addMouseListener(eventExecutor);//listen when not editable } } public boolean isEditable() { return enclosedComponent.isEditable(); } private LinkListener linkListener; class LinkListener implements HyperlinkListener { public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) { if (!DataTextEditor.this.isEnabled()) return; HyperlinkEvent.EventType type = hyperlinkEvent.getEventType(); if (type == HyperlinkEvent.EventType.ACTIVATED) { try { String description = hyperlinkEvent.getDescription(); if (description != null && description.toLowerCase().startsWith("javascript:")) //$NON-NLS-1$ { String script = description; if (script.length() > 13) { String scriptName = script.substring(11); Container parent = getParent(); while (parent != null && !(parent instanceof SwingForm)) { parent = parent.getParent(); } if (parent instanceof SwingForm) { ((SwingForm)parent).getController().eval(scriptName); } else { ((FormController)application.getFormManager().getCurrentForm()).eval(scriptName); } } } URL url = hyperlinkEvent.getURL(); if (url == null || "javascript".equals(url.getProtocol())) return; if (hyperlinkEvent instanceof HTMLFrameHyperlinkEvent) { HTMLFrameHyperlinkEvent link = (HTMLFrameHyperlinkEvent)hyperlinkEvent; if ("_blank".equalsIgnoreCase(link.getTarget())) //$NON-NLS-1$ { application.showURL(url.toString(), null, null, 0, true); return; } } else { try { enclosedComponent.setPage(url); } catch (Exception e) { Debug.trace("exception setting page as url in DataTextEditor, url: " + url); //$NON-NLS-1$ } } } catch (Exception e) { if (e instanceof ExitScriptException) { // ignore return; } Debug.error(e); } } else if (type == HyperlinkEvent.EventType.ENTERED) { enclosedComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } // else if (hyperlinkEvent$Event2 != HyperlinkEvent.EventType.EXITED) // { // } else { enclosedComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } @Override public void addFocusListener(FocusListener fl) { if (enclosedComponent != null) enclosedComponent.addFocusListener(fl); } @Override public void removeFocusListener(FocusListener fl) { if (enclosedComponent != null) enclosedComponent.removeFocusListener(fl); } public String getDataProviderID() { return dataProviderID; } public void setDataProviderID(String id) { dataProviderID = id; } private Object previousValue; private void setValueThreadSafe(final Object value) { try { Runnable runnable = new Runnable() { public void run() { try { setValueEx(value); } catch (Exception e) { Debug.error("error setting a vallue in the html editor: " + value, e); //$NON-NLS-1$ if (editorKit instanceof FixedHTMLEditorKit) { Debug.error("creating a new document on the html editor kit and setting the value again"); //$NON-NLS-1$ enclosedComponent.setDocument(editorKit.createDefaultDocument()); setValueEx(value); } } } }; if (application.isEventDispatchThread()) { // if on event thread make sure next call to getValueObject() gets this value runnable.run(); } else { // do not block when called from another thread, may cause deadlock when ui thread is busy application.invokeLater(runnable); } } catch (Exception e) { Debug.error(e); } } private void setValueEx(Object value) { try { if (value == null) { String prevValue = (String)getValueObject(); if (prevValue != null && prevValue.toLowerCase().indexOf("<html") > -1) //$NON-NLS-1$ { value = "<html><body></body></html>"; //$NON-NLS-1$ } } if (editProvider != null) editProvider.setAdjusting(true); if (!Utils.equalObjects(previousValue, value)) { previousValue = value; if (value != null) { int selStart = enclosedComponent.getSelectionStart(); int selEnd = enclosedComponent.getSelectionEnd(); String svalue = value.toString(); String lowercaseValue = svalue.toLowerCase(); if (lowercaseValue.indexOf("rtf") != -1 || lowercaseValue.indexOf("<html") != -1) //$NON-NLS-1$ //$NON-NLS-2$ { if (svalue.length() > 10000 && application.getModeManager().getMode() == IModeManager.EDIT_MODE && isAsyncLoading() && lowercaseValue.indexOf("<html") != -1) { ByteArrayInputStream bais = new ByteArrayInputStream(svalue.getBytes("UTF-8")); //$NON-NLS-1$ enclosedComponent.read(bais, null); } else { cancelLoadAndClearDocument(); enclosedComponent.putClientProperty(FixedJEditorPane.CHARSET_DIRECTIVE, "UTF-8"); enclosedComponent.getDocument().putProperty("IgnoreCharsetDirective", new Boolean(true)); StringReader sr = new StringReader(svalue); editorKit.read(sr, enclosedComponent.getDocument(), 0); } // now go over all the generated components and try to add // action listeners to the buttons (checkbox,radio) // so that clicks on that also result in the onaction of this htmlarea for (Component c : enclosedComponent.getComponents()) { Container parent = (Container)c; if (parent.getComponents().length == 1 && parent.getComponents()[0] instanceof AbstractButton) { ((AbstractButton)parent.getComponents()[0]).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { eventExecutor.actionPerformed(e.getModifiers()); } }); } } } else { cancelLoadAndClearDocument(); enclosedComponent.getDocument().insertString(0, svalue, null); } if (selStart <= enclosedComponent.getDocument().getLength() && selEnd <= enclosedComponent.getDocument().getLength()) enclosedComponent.select( selStart, selEnd); else enclosedComponent.setCaretPosition(0); } else { cancelLoadAndClearDocument(); } if (enclosedComponent.getWidth() == 0) { enclosedComponent.setSize(getSize()); doLayout(); } } } catch (Exception ex) { Debug.error(ex); enclosedComponent.setText("<html><body></body></html>"); } finally { if (editProvider != null) editProvider.setAdjusting(false); } } private void cancelLoadAndClearDocument() { // cancel an async load that could have been done the previous time enclosedComponent.cancelASyncLoad(); // always just create a new document, asyn load will also do that. enclosedComponent.setDocument(editorKit.createDefaultDocument()); } public boolean needEntireState() { return needEntireState; } private boolean needEntireState; private ArrayList<ILabel> labels; public void setNeedEntireState(boolean b) { needEntireState = b; } protected ITagResolver resolver; public void setTagResolver(ITagResolver resolver) { this.resolver = resolver; } public void setValueObject(Object obj) { if (needEntireState) { if (resolver != null) { if (dataProviderID == null && !isEditable()) { setValueThreadSafe(Text.processTags(tagText, resolver)); } else { if (obj != null) { String val = Text.processTags(resolver.getStringValue(dataProviderID), resolver); setValueThreadSafe(val); } else { setValueThreadSafe(null); } } if (tooltip != null) { enclosedComponent.setToolTipText(Text.processTags(tooltip, resolver)); } } else { setValueThreadSafe(null); if (tooltip != null) { enclosedComponent.setToolTipText(null); } } } else { setValueThreadSafe(obj); if (tooltip != null) { enclosedComponent.setToolTipText(tooltip); } } fireOnRender(false); } public void fireOnRender(boolean force) { if (scriptable != null) { if (force) scriptable.getRenderEventExecutor().setRenderStateChanged(); scriptable.getRenderEventExecutor().fireOnRender(enclosedComponent.hasFocus()); } } public Object getValueObject() { if (eventExecutor.getValidationEnabled()) { try { if (editorKit instanceof FixedRTFEditorKit) { // RTF area contents always use ISO Latin-1; we use the byte array because write(writer,..) throws exception ByteArrayOutputStream os = new ByteArrayOutputStream(); editorKit.write(os, enclosedComponent.getDocument(), 0, enclosedComponent.getDocument().getLength()); return os.toString("ISO-8859-1"); } else { // HTML area can use any charset - so we get contents using a writer, to avoid charset problems StringWriter sw = new StringWriter(); editorKit.write(sw, enclosedComponent.getDocument(), 0, enclosedComponent.getDocument().getLength()); return sw.toString(); } } catch (Exception ex) { Debug.error(ex); return ""; //$NON-NLS-1$ } } return enclosedComponent.getText(); } @Override public void requestFocus() { if (enclosedComponent != null) enclosedComponent.requestFocus(); } @Override public boolean requestFocus(boolean temporary) { if (enclosedComponent != null) return enclosedComponent.requestFocus(temporary); else return super.requestFocus(temporary); } @Override public void setBackground(Color bg) { if (enclosedComponent != null) enclosedComponent.setBackground(bg); super.setBackground(bg); } @Override public Color getBackground() { if (enclosedComponent != null) { return enclosedComponent.getBackground(); } return super.getBackground(); } @Override public Color getForeground() { if (enclosedComponent != null) { return enclosedComponent.getForeground(); } return super.getForeground(); } @Override public void setForeground(Color fg) { if (enclosedComponent != null) enclosedComponent.setForeground(fg); super.setForeground(fg); } public void setComponentVisible(boolean b_visible) { if (viewable || !b_visible) { setVisible(b_visible); } } @Override public void setVisible(boolean flag) { super.setVisible(flag); if (labels != null) { for (int i = 0; i < labels.size(); i++) { ILabel label = labels.get(i); label.setComponentVisible(flag); } } } public void addLabelFor(ILabel label) { if (labels == null) labels = new ArrayList<ILabel>(3); labels.add(label); } public List<ILabel> getLabelsFor() { return labels; } @Override public void setOpaque(boolean b) { if (enclosedComponent != null) enclosedComponent.setOpaque(b); getViewport().setOpaque(b); super.setOpaque(b); } public void setComponentEnabled(final boolean b) { if (accessible || !b) { super.setEnabled(b); if (labels != null) { for (int i = 0; i < labels.size(); i++) { ILabel label = labels.get(i); label.setComponentEnabled(b); } } } } private boolean accessible = true; public void setAccessible(boolean b) { if (!b) setComponentEnabled(b); accessible = b; } private boolean viewable = true; public void setViewable(boolean b) { if (!b) setComponentVisible(b); this.viewable = b; } public boolean isViewable() { return viewable; } /* * readonly--------------------------------------------------- */ public boolean isReadOnly() { return !enclosedComponent.isEditable(); } private boolean editState; public void setReadOnly(boolean b) { if (b && !enclosedComponent.isEditable()) return; if (b) { setEditable(false); editState = true; } else { setEditable(editState); } } /* * titleText--------------------------------------------------- */ private String titleText = null; public void setTitleText(String title) { this.titleText = title; } public String getTitleText() { return Text.processTags(titleText, resolver); } /* * tooltip--------------------------------------------------- */ private String tooltip; @Override public void setToolTipText(String tip) { if (tip != null && tip.indexOf("%%") != -1) //$NON-NLS-1$ { tooltip = tip; } else if (!Utils.stringIsEmpty(tip)) { if (!Utils.stringContainsIgnoreCase(tip, "<html")) //$NON-NLS-1$ { enclosedComponent.setToolTipText(tip); } else if (HtmlUtils.hasUsefulHtmlContent(tip)) { enclosedComponent.setToolTipText(tip); } } else { enclosedComponent.setToolTipText(null); } } @Override public String getToolTipText(MouseEvent event) { String txt = super.getToolTipText(event); if (txt == null || txt.length() == 0) { return enclosedComponent.getToolTipText(event); } return null; } public int getAbsoluteFormLocationY() { Container parent = getParent(); while ((parent != null) && !(parent instanceof IDataRenderer)) { parent = parent.getParent(); } if (parent != null) { return ((IDataRenderer)parent).getYOffset() + getLocation().y; } return getLocation().y; } private Point cachedLocation; public Point getCachedLocation() { return cachedLocation; } public void setCachedLocation(Point location) { this.cachedLocation = location; } public void setCachedSize(Dimension size) { this.cachedSize = size; } private Dimension cachedSize; public Dimension getCachedSize() { return cachedSize; } // If component not shown or not added yet // and request focus is called it should wait for the component // to be created. boolean wantFocus = false; @Override public void addNotify() { super.addNotify(); if (wantFocus) { wantFocus = false; enclosedComponent.requestFocus(); } } public void requestFocusToComponent() { // if (!enclosedComponent.hasFocus()) Don't test on hasFocus (it can have focus,but other component already did requestFocus) { if (isDisplayable()) { // Must do it in a runnable or else others after a script can get focus first again.. application.invokeLater(new Runnable() { public void run() { enclosedComponent.requestFocus(); } }); } else { wantFocus = true; } } } @Override public String toString() { return scriptable.toString(); } public boolean stopUIEditing(boolean looseFocus) { if (editProvider != null) editProvider.forceCommit(); if (!isValueValid) { application.invokeLater(new Runnable() { public void run() { requestFocus(); } }); return false; } if (looseFocus && eventExecutor.mustFireFocusLostCommand()) { eventExecutor.skipNextFocusLost(); eventExecutor.fireLeaveCommands(this, false, IEventExecutor.MODIFIERS_UNSPECIFIED); } return true; } boolean asyncLoading = true; public void setAsyncLoadingEnabled(boolean b) { if (editorKit instanceof ISupportAsyncLoading) { ((ISupportAsyncLoading)editorKit).setAsyncLoadingEnabled(b); } asyncLoading = b; } private boolean isAsyncLoading() { return asyncLoading && !Utils.getAsBoolean(application.getRuntimeProperties().get("isPrinting")); //$NON-NLS-1$ } public String getId() { return (String)getClientProperty("Id"); } private int preferredWidth = -1; public void setPreferredWidth(int preferredWidth) { this.preferredWidth = preferredWidth; } @Override public Dimension getPreferredSize() { if (preferredWidth < 0) { return super.getPreferredSize(); } else { Dimension preferredSize = new Dimension(); preferredSize.width = preferredWidth; // calculate preferred height based on the given width Insets insets = getInsets(); Dimension oldSize = enclosedComponent.getSize(); enclosedComponent.setSize(preferredWidth - insets.left - insets.right, getHeight() - insets.top - insets.bottom); preferredSize.height = super.getPreferredSize().height; enclosedComponent.setSize(oldSize); return preferredSize; } } /* * @see com.servoy.j2db.dnd.ISupportTextTransfer#clearTransferHandler() */ public void clearTransferHandler() { setTransferHandler(null); if (enclosedComponent instanceof ISupportDragNDropTextTransfer) ((ISupportDragNDropTextTransfer)enclosedComponent).clearTransferHandler(); } /* * @see com.servoy.j2db.dnd.ISupportTextTransfer#getTextTransferHandler() */ public TransferHandler getTextTransferHandler() { return enclosedComponent instanceof ISupportDragNDropTextTransfer ? ((ISupportDragNDropTextTransfer)enclosedComponent).getTextTransferHandler() : null; } /* * (non-Javadoc) * * @see com.servoy.j2db.ui.IDisplayTagText#setTagText(java.lang.String) */ public void setTagText(String tagText) { this.tagText = tagText; } /* * (non-Javadoc) * * @see com.servoy.j2db.ui.IDisplayTagText#getTagText() */ public String getTagText() { return tagText; } }