All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wings.STextComponent Maven / Gradle / Ivy

/*
 * $Id$
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings;

import org.wings.event.SDocumentEvent;
import org.wings.event.SDocumentListener;
import org.wings.event.SMouseEvent;
import org.wings.plaf.TextAreaCG;
import org.wings.plaf.TextFieldCG;
import org.wings.text.DefaultDocument;
import org.wings.text.SDocument;
import org.wings.sdnd.TextAndHTMLTransferable;
import org.wings.sdnd.SDropMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.text.BadLocationException;
import javax.swing.*;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Abstract base class of input text components like {@link STextArea} and {@link STextField}.
 * Requires a surrounding {@link SForm} element!
 *
 * @author Armin Haaf
 * @version $Revision$
 */
public abstract class STextComponent extends SComponent implements LowLevelEventListener, SDocumentListener {
    private static final Logger LOG = LoggerFactory.getLogger(STextComponent.class);

    private boolean editable = true;

    private SDocument document;

    /**
     * @see LowLevelEventListener#isEpochCheckEnabled()
     */
    private boolean epochCheckEnabled = true;

    public STextComponent() {
        this(new DefaultDocument(), true);
    }

    public STextComponent(String text) {
        this(new DefaultDocument(text), true);
    }

    public STextComponent(SDocument document) {
        this(document, true);
    }

    public STextComponent(SDocument document, boolean editable) {
        setDocument(document);
        setEditable(editable);
        installTransferHandler();
        createActionMap();
        if(!(this instanceof STextField)) {
            setDropMode(SDropMode.USE_SELECTION);
        }
    }

    public SDocument getDocument() {
        return document;
    }

    public void setDocument(SDocument document) {
        if (document == null)
            throw new IllegalArgumentException("null");

        SDocument oldDocument = this.document;
        this.document = document;
        if (oldDocument != null)
            oldDocument.removeDocumentListener(this);
        document.addDocumentListener(this);
        reloadIfChange(oldDocument, document);
        propertyChangeSupport.firePropertyChange("document", oldDocument, this.document);
    }

    /**
     * Defines if the textcomponent is editable or not.
     * @see #isEditable()
     * @param ed true if the text component is to be editable false if not.
     */
    public void setEditable(boolean ed) {
        boolean oldEditable = editable;
        editable = ed;
        if (editable != oldEditable)
            reload();
        propertyChangeSupport.firePropertyChange("editable", oldEditable, this.editable);
    }


    public boolean isEditable() {
        return editable;
    }

    /**
     * Sets the text of the component to the specified text.
     * @see #getText()
     * @param text the new text for the component.
     */
    public void setText(String text) {
        String oldVal = document.getText();
        document.setText(text);
        propertyChangeSupport.firePropertyChange("text", oldVal, document.getText());
    }


    public String getText() {
        return document.getText();
    }

    /**
     * Appends the given text to the end of the document. Does nothing
     * if the string is null or empty.
     *
     * @param text the text to append.
     */
    public void append(String text) {
        try {
            document.insert(document.getLength(), text);
        } catch (BadLocationException e) {
        }
    }

    @Override
    public void processLowLevelEvent(String action, String... values) {
        processKeyEvents(values);
        if (action.endsWith("_keystroke"))
            return;

        if (isEditable() && isEnabled()) {
            String newValue = values[0];
            if (newValue != null && newValue.length() == 0) {
                newValue = null;
            }
            String text = getText();
            if (text != null && text.length() == 0) {
                text = null;
            }

            if (isDifferent(newValue, text)) {
                document.setDelayEvents(true);
                setText(newValue);
                document.setDelayEvents(false);
                SForm.addArmedComponent(this);
            }
        }
    }

    public SDocumentListener[] getDocumentListeners() {
        return document.getDocumentListeners();
    }

    public void addDocumentListener(SDocumentListener listener) {
        document.addDocumentListener(listener);
    }

    public void removeDocumentListener(SDocumentListener listener) {
        document.removeDocumentListener(listener);
    }

    @Override
    public void fireIntermediateEvents() {
        document.fireDelayedIntermediateEvents();
    }

    @Override
    public void fireFinalEvents() {
        super.fireFinalEvents();
        document.fireDelayedFinalEvents();
    }

    /**
     * @see LowLevelEventListener#isEpochCheckEnabled()
     */
    @Override
    public boolean isEpochCheckEnabled() {
        return epochCheckEnabled;
    }

    /**
     * @see LowLevelEventListener#isEpochCheckEnabled()
     */
    public void setEpochCheckEnabled(boolean epochCheckEnabled) {
        boolean oldVal = this.epochCheckEnabled;
        this.epochCheckEnabled = epochCheckEnabled;
        propertyChangeSupport.firePropertyChange("epochCheckEnabled", oldVal, this.epochCheckEnabled);
    }

    @Override
    public void insertUpdate(SDocumentEvent e) {
        //reload();
    }

    @Override
    public void removeUpdate(SDocumentEvent e) {
        //reload();
    }

    @Override
    public void changedUpdate(SDocumentEvent e) {
        if (isUpdatePossible()) {
            if (STextField.class.isAssignableFrom(getClass()) && ! SPasswordField.class.isAssignableFrom(getClass()))
                update(((TextFieldCG) getCG()).getTextUpdate((STextField) this, getText()));
            else if (STextArea.class.isAssignableFrom(getClass()))
                update(((TextAreaCG) getCG()).getTextUpdate((STextArea) this, getText()));
            else
                reload();
        } else {
            reload();
        }
    }

    /**
     * Drag and Drop stuff
     */
    private int selectionStart;
    private int selectionEnd;
    private int caretPosition;

    protected void createActionMap() {
        ActionMap map = getActionMap();

        map.put(STransferHandler.getCutAction().getValue(Action.NAME), STransferHandler.getCutAction());
        map.put(STransferHandler.getCopyAction().getValue(Action.NAME), STransferHandler.getCopyAction());
        map.put(STransferHandler.getPasteAction().getValue(Action.NAME), STransferHandler.getPasteAction());
    }

    public void setCaretPosition(int caretPosition) {
        this.caretPosition = caretPosition;
    }

    /**
     * Note: Used for Clipboard - only updated if done by setCaretPosition
     * @return
     */
    public int getCaretPosition() {
        return caretPosition;
    }

    /**
     * Internal Selection Mechanism for Drag and Drop - may be made public if a real selection support is introduced
     * @return
     */
    public int getSelectionStart() {
        return selectionStart;
    }

    /**
     * Internal Selection Mechanism for Drag and Drop - may be made public if a real selection support is introduced
     * @param selectionStart
     */
    public void setSelectionStart(int selectionStart) {
        this.selectionStart = selectionStart;
    }

    /**
     * Internal Selection Mechanism for Drag and Drop - may be made public if a real selection support is introduced
     * @return
     */
    public int getSelectionEnd() {
        return selectionEnd;
    }

    /**
     * Internal Selection Mechanism for Drag and Drop - may be made public if a real selection support is introduced
     * @param selectionEnd
     */
    public void setSelectionEnd(int selectionEnd) {
        this.selectionEnd = selectionEnd;
    }

    /**
     * Internal Selection Mechanism for Drag and Drop - may be made public if a real selection support is introduced
     * @return
     */
    protected String getSelectedText() {
        String text = getText();
        return text.substring(selectionStart, selectionEnd);
    }

    protected boolean dragEnabled = false;
    protected SDropMode dropMode = null;

    /**
     * Sets the DropMode for this component - supports USE_SELECTION and INSERT
     * @param dropMode
     */
    public void setDropMode(SDropMode dropMode) {
        if(dropMode == null || (dropMode != SDropMode.USE_SELECTION))
            throw new IllegalArgumentException(dropMode + " - unsupported DropMode");

        if(this instanceof STextField) {
            LOG.warn("setDropMode: STextField as DropTarget will show different drop-behaviour in IE than in FF");
        }
        this.dropMode = dropMode;
        getSession().getSDragAndDropManager().addDropTarget(this);
    }

    private void installTransferHandler() {
        if(getTransferHandler() == null) {
            setTransferHandler(new DefaultTransferHandler());
        }
    }

    public SDropMode getDropMode() {
        return this.dropMode;
    }

    public void setDragEnabled(boolean dragEnabled) {
        if(dragEnabled != this.dragEnabled) {
            if(dragEnabled)
                this.getSession().getSDragAndDropManager().addDragSource(this);
            else
                this.getSession().getSDragAndDropManager().removeDragSource(this);

            this.dragEnabled = dragEnabled;
        }
    }

    public boolean getDragEnabled() {
        return this.dragEnabled;
    }

    @Override
    protected DropLocation dropLocationForPoint(SPoint point) {
        if(point.getCoordinates() == null)
            return null;
        if(point.getCoordinates().contains("-") && point.getCoordinates().indexOf("-1") != 0) // dragstart => dragenter event
            return null;
        return new DropLocation(point);
    }

    public static final class DropLocation extends STransferHandler.DropLocation {
        private int index;

        public DropLocation(SPoint point) {
            super(point);

            try {
                this.index = Integer.parseInt(point.getCoordinates());
                if(this.index == -1)
                    this.index = 0;
            } catch(NumberFormatException e) {
                this.index = -1;
                e.printStackTrace();
            }
        }

        public int getIndex() {
            return this.index;
        }
    }

    private static class TextTransferable extends TextAndHTMLTransferable {
        private int startPos, endPos;
        private STextComponent component;
        private STextComponent destComponent;

        protected TextTransferable(STextComponent component, int startPos, int endPos) {
            super(null, null);

            this.startPos = startPos;
            this.endPos = endPos;
            this.component = component;

            this.plainTextData = component.getSelectedText();
        }

        protected void setDestComponent(STextComponent component) {
            this.destComponent = component;
        }

        protected void removeText() {
            try {
                component.getDocument().remove(this.startPos, this.endPos - this.startPos);
                component.reload();
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
        }
    }

    public static class DefaultTransferHandler extends STransferHandler {
        public DefaultTransferHandler() {
            super("text");
        }
        
        @Override
        public void exportDone(SComponent source, Transferable transferable, int action) {
            if(action == MOVE) {
                if(transferable instanceof TextTransferable) {
                    ((TextTransferable)transferable).removeText();
                }
            }

            super.exportDone(source, transferable, action);
        }

        @Override
        protected Transferable createTransferable(SComponent component) {
            if(component instanceof STextComponent) {
                STextComponent textComponent = (STextComponent)component;
                return new TextTransferable(textComponent, textComponent.getSelectionStart(), textComponent.getSelectionEnd());
            }
            
            return null;
        }

        @Override
        protected Transferable createTransferable(SComponent component, SMouseEvent event) {
            if(component instanceof STextComponent) {
                STextComponent textComponent = (STextComponent)component;

                String[] stringArray = event.getPoint().getCoordinates().split("-");
            
                int startIndex = Integer.parseInt(stringArray[0]);
                int endIndex = Integer.parseInt(stringArray[1]);
                textComponent.setSelectionStart(startIndex);
                textComponent.setSelectionEnd(endIndex);
            }

            return super.createTransferable(component, event);
        }

        protected static DataFlavor getImportFlavor(STextComponent component, DataFlavor... flavors) {
            for(DataFlavor flavor:flavors) {
                if(flavor.getMimeType().startsWith("text/plain") || flavor.getMimeType().startsWith(DataFlavor.javaJVMLocalObjectMimeType) && flavor.getRepresentationClass() == String.class) {
                    return flavor;
                }
            }
            return null;
        }

        @Override
        public boolean canImport(SComponent component, DataFlavor... transferFlavors) {
            STextComponent textComponent = (STextComponent)component;
            if(!textComponent.isEditable() || !textComponent.isEnabled()) // if the component is not editable
                return false;                                               // or disabled, don't allow imports

            return getImportFlavor(textComponent, transferFlavors) != null;

        }

        @Override
        public boolean importData(SComponent component, Transferable transferable) {
            try {
                STextComponent textComponent = (STextComponent)component;

                String data = (String)(transferable.getTransferData(getImportFlavor((STextComponent)component, transferable.getTransferDataFlavors())));
                String text = textComponent.getText();
                int insertIndex = textComponent.getCaretPosition();

                if(insertIndex == -1) { // in case we couldn't determine a drop position, append
                    textComponent.setText(text + data);
                    return true;
                }

                if(insertIndex > text.length())
                    return false;

                if(textComponent.getSelectionStart() == textComponent.getSelectionEnd()) {
                    String firstPart = text.substring(0, insertIndex);
                    String secondPart = text.substring(insertIndex);

                    if(transferable instanceof TextTransferable) {
                        ((TextTransferable)transferable).setDestComponent((STextComponent)component);
                    }
                    textComponent.setText(firstPart + data + secondPart);
                } else {
                    String firstPart = text.substring(0, textComponent.getSelectionStart());
                    String secondPart = text.substring(textComponent.getSelectionEnd());
                    textComponent.setText(firstPart + data + secondPart);
                }

                return true;
            } catch (UnsupportedFlavorException | IOException e) {
            }
            return false;
        }

        @Override
        public boolean importData(TransferSupport support) {
            if(support.isDrop()) {
                int index = ((STextComponent.DropLocation)support.getDropLocation()).getIndex();
                ((STextComponent)support.getComponent()).setCaretPosition(index);
                ((STextComponent)support.getComponent()).setSelectionStart(index);
                ((STextComponent)support.getComponent()).setSelectionEnd(index);
            }

            return super.importData(support);
        }

        @Override
        public int getSourceActions(SComponent component) {
            if(component instanceof SPasswordField) // don't allow drag/drop from password fields
                return NONE;

            if(!((STextComponent)component).isEditable()) // don't allow MOVE when the component isn't editable
                return COPY;
            
            return COPY_OR_MOVE;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy