src/de/matthiasmann/twl/EditField.java
author Matthias Mann
Mon Mar 01 18:37:39 2010 +0100 (2010-03-01)
changeset 431 cefe1c8066a5
parent 387 bc41d0745c99
child 432 ece24ea8c1d4
permissions -rw-r--r--
added alt/menu key support to input maps
mam@0
     1
/*
Matthias@270
     2
 * Copyright (c) 2008-2010, Matthias Mann
mam@0
     3
 * 
mam@0
     4
 * All rights reserved.
mam@0
     5
 * 
mam@0
     6
 * Redistribution and use in source and binary forms, with or without
mam@0
     7
 * modification, are permitted provided that the following conditions are met:
mam@0
     8
 * 
mam@0
     9
 *     * Redistributions of source code must retain the above copyright notice,
mam@0
    10
 *       this list of conditions and the following disclaimer.
mam@0
    11
 *     * Redistributions in binary form must reproduce the above copyright
mam@0
    12
 *       notice, this list of conditions and the following disclaimer in the
mam@0
    13
 *       documentation and/or other materials provided with the distribution.
mam@0
    14
 *     * Neither the name of Matthias Mann nor the names of its contributors may
mam@0
    15
 *       be used to endorse or promote products derived from this software
mam@0
    16
 *       without specific prior written permission.
mam@0
    17
 * 
mam@0
    18
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
mam@0
    19
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
mam@0
    20
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
mam@0
    21
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
mam@0
    22
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
mam@0
    23
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
mam@0
    24
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
mam@0
    25
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
mam@0
    26
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
mam@0
    27
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
mam@0
    28
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mam@0
    29
 */
mam@0
    30
package de.matthiasmann.twl;
mam@0
    31
Matthias@59
    32
import de.matthiasmann.twl.model.StringModel;
mam@0
    33
import de.matthiasmann.twl.utils.TextUtil;
mam@0
    34
import de.matthiasmann.twl.utils.CallbackSupport;
mam@0
    35
import de.matthiasmann.twl.renderer.Font;
mam@0
    36
import de.matthiasmann.twl.renderer.Image;
mam@0
    37
import org.lwjgl.input.Keyboard;
mam@0
    38
mam@0
    39
/**
mam@0
    40
 * A simple one line edit field
mam@0
    41
 * 
mam@0
    42
 * @author Matthias Mann
mam@0
    43
 */
mam@0
    44
public class EditField extends Widget {
mam@0
    45
Matthias@110
    46
    public static final String STATE_ERROR = "error";
Matthias@233
    47
    public static final String STATE_READONLY = "readonly";
Matthias@358
    48
    public static final String STATE_HOVER = "hover";
Matthias@110
    49
    
mam@0
    50
    public interface Callback {
Matthias@270
    51
        /**
Matthias@270
    52
         * Gets called for any change in the edit field, or when ESCAPE or RETURN was pressed
Matthias@270
    53
         *
Matthias@270
    54
         * @param key One of KEY_NONE, KEY_ESCAPE, KEY_RETURN
Matthias@270
    55
         * @see Keyboard#KEY_NONE
Matthias@270
    56
         * @see Keyboard#KEY_ESCAPE
Matthias@270
    57
         * @see Keyboard#KEY_RETURN
Matthias@270
    58
         */
mam@0
    59
        public void callback(int key);
mam@0
    60
    }
mam@0
    61
mam@0
    62
    private final StringBuilder editBuffer;
Matthias@313
    63
    private final TextRenderer textRenderer;
mam@0
    64
    private PasswordMasker passwordMasking;
Matthias@59
    65
    private Runnable modelChangeListener;
Matthias@59
    66
    private StringModel model;
Matthias@233
    67
    private boolean readOnly;
mam@0
    68
mam@0
    69
    private int cursorPos;
mam@0
    70
    private int scrollPos;
mam@0
    71
    private int selectionStart;
mam@0
    72
    private int selectionEnd;
mam@0
    73
    private int maxTextLength = Short.MAX_VALUE;
mam@0
    74
Matthias@313
    75
    private int columns = 5;
mam@0
    76
    private Image cursorImage;
mam@0
    77
    private Image selectionImage;
mam@0
    78
    private char passwordChar;
mam@0
    79
    private Object errorMsg;
mam@0
    80
    private Callback[] callbacks;
mam@0
    81
    private PopupMenu popupMenu;
Matthias@237
    82
    private boolean textLongerThenWidget;
Matthias@270
    83
Matthias@270
    84
    private InfoWindow autoCompletionWindow;
Matthias@270
    85
    private boolean autoCompletionWantKeys;
Matthias@270
    86
    private int autoCompletionHeight = 100;
Matthias@352
    87
Matthias@352
    88
    private InfoWindow errorInfoWindow;
Matthias@352
    89
    private Label errorInfoLabel;
Matthias@363
    90
Matthias@363
    91
    /**
Matthias@363
    92
     * Creates a new EditField with an optional parent animation state.
Matthias@363
    93
     *
Matthias@363
    94
     * Unlike other widgets which use the passed animation state directly,
Matthias@363
    95
     * the EditField always creates it's animation state with the passed
Matthias@363
    96
     * one as parent.
Matthias@363
    97
     *
Matthias@363
    98
     * @param parentAnimationState
Matthias@363
    99
     * @see AnimationState#AnimationState(de.matthiasmann.twl.AnimationState) 
Matthias@363
   100
     */
Matthias@363
   101
    public EditField(AnimationState parentAnimationState) {
Matthias@364
   102
        super(parentAnimationState, true);
Matthias@363
   103
        
mam@0
   104
        this.editBuffer = new StringBuilder();
mam@0
   105
        this.textRenderer = new TextRenderer(getAnimationState());
mam@0
   106
        this.passwordChar = '*';
mam@0
   107
mam@0
   108
        textRenderer.setTheme("renderer");
mam@0
   109
        textRenderer.setClip(true);
mam@0
   110
        
mam@0
   111
        add(textRenderer);
mam@0
   112
        setCanAcceptKeyboardFocus(true);
Matthias@136
   113
        setDepthFocusTraversal(false);
Matthias@135
   114
Matthias@135
   115
        addActionMapping("cut", "cutToClipboard");
Matthias@135
   116
        addActionMapping("copy", "copyToClipboard");
Matthias@135
   117
        addActionMapping("paste", "pasteFromClipboard");
Matthias@135
   118
        addActionMapping("selectAll", "selectAll");
mam@0
   119
    }
mam@0
   120
Matthias@363
   121
    public EditField() {
Matthias@363
   122
        this(null);
Matthias@363
   123
    }
Matthias@363
   124
mam@0
   125
    public void addCallback(Callback cb) {
mam@0
   126
        callbacks = CallbackSupport.addCallbackToList(callbacks, cb, Callback.class);
mam@0
   127
    }
mam@0
   128
mam@0
   129
    public void removeCallback(Callback cb) {
Matthias@182
   130
        callbacks = CallbackSupport.removeCallbackFromList(callbacks, cb);
mam@0
   131
    }
mam@0
   132
mam@0
   133
    protected void doCallback(int key) {
mam@0
   134
        if(callbacks != null) {
mam@0
   135
            for(Callback cb : callbacks) {
mam@0
   136
                cb.callback(key);
mam@0
   137
            }
mam@0
   138
        }
mam@0
   139
    }
mam@0
   140
mam@0
   141
    public boolean isPasswordMasking() {
mam@0
   142
        return passwordMasking != null;
mam@0
   143
    }
mam@0
   144
mam@0
   145
    public void setPasswordMasking(boolean passwordMasking) {
mam@0
   146
        if(passwordMasking != isPasswordMasking()) {
mam@0
   147
            if(passwordMasking) {
mam@0
   148
                this.passwordMasking = new PasswordMasker(editBuffer, passwordChar);
mam@0
   149
            } else {
mam@0
   150
                this.passwordMasking = null;
mam@0
   151
            }
mam@0
   152
            updateText();
mam@0
   153
        }
mam@0
   154
    }
mam@0
   155
mam@0
   156
    public char getPasswordChar() {
mam@0
   157
        return passwordChar;
mam@0
   158
    }
mam@0
   159
mam@0
   160
    public void setPasswordChar(char passwordChar) {
mam@0
   161
        this.passwordChar = passwordChar;
mam@0
   162
        if(passwordMasking != null) {
mam@0
   163
            passwordMasking = new PasswordMasker(editBuffer, passwordChar);
mam@0
   164
            updateText();
mam@0
   165
        }
mam@0
   166
    }
mam@0
   167
Matthias@313
   168
    public int getColumns() {
Matthias@313
   169
        return columns;
Matthias@313
   170
    }
Matthias@313
   171
Matthias@313
   172
    /**
Matthias@313
   173
     * This is used to determine the desired width of the EditField based on
Matthias@313
   174
     * it's font and the character 'X'
Matthias@313
   175
     * 
Matthias@313
   176
     * @param columns number of characters
Matthias@313
   177
     * @throws IllegalArgumentException if columns < 0
Matthias@313
   178
     */
Matthias@313
   179
    public void setColumns(int columns) {
Matthias@313
   180
        if(columns < 0) {
Matthias@313
   181
            throw new IllegalArgumentException("columns");
Matthias@313
   182
        }
Matthias@313
   183
        this.columns = columns;
Matthias@313
   184
    }
Matthias@313
   185
Matthias@59
   186
    public StringModel getModel() {
Matthias@59
   187
        return model;
Matthias@59
   188
    }
Matthias@59
   189
Matthias@59
   190
    public void setModel(StringModel model) {
Matthias@59
   191
        if(this.model != null) {
Matthias@59
   192
            this.model.removeCallback(modelChangeListener);
Matthias@59
   193
        }
Matthias@59
   194
        this.model = model;
Matthias@59
   195
        if(this.model != null) {
Matthias@59
   196
            if(modelChangeListener == null) {
Matthias@59
   197
                modelChangeListener = new ModelChangeListener();
Matthias@59
   198
            }
Matthias@59
   199
            this.model.addCallback(modelChangeListener);
Matthias@59
   200
            modelChanged();
Matthias@59
   201
        }
Matthias@59
   202
    }
Matthias@59
   203
mam@0
   204
    public void setText(String text) {
mam@0
   205
        text = TextUtil.limitStringLength(text, maxTextLength);
mam@0
   206
        editBuffer.replace(0, editBuffer.length(), text);
mam@0
   207
        cursorPos = editBuffer.length();
Matthias@75
   208
        selectionStart = 0;
Matthias@75
   209
        selectionEnd = 0;
mam@0
   210
        updateText();
mam@0
   211
        scrollToCursor(true);
mam@0
   212
    }
mam@0
   213
mam@0
   214
    public String getText() {
mam@0
   215
        return editBuffer.toString();
mam@0
   216
    }
mam@0
   217
    
mam@0
   218
    public String getSelectedText() {
mam@0
   219
        return editBuffer.substring(selectionStart, selectionEnd);
mam@0
   220
    }
mam@0
   221
mam@0
   222
    public boolean hasSelection() {
mam@0
   223
        return selectionStart != selectionEnd;
mam@0
   224
    }
mam@0
   225
Matthias@270
   226
    public int getCursorPos() {
Matthias@270
   227
        return cursorPos;
Matthias@270
   228
    }
Matthias@270
   229
Matthias@270
   230
    public int getTextLength() {
Matthias@270
   231
        return editBuffer.length();
Matthias@270
   232
    }
Matthias@270
   233
    
Matthias@233
   234
    public boolean isReadOnly() {
Matthias@233
   235
        return readOnly;
Matthias@233
   236
    }
Matthias@233
   237
Matthias@233
   238
    public void setReadOnly(boolean readOnly) {
Matthias@233
   239
        this.readOnly = readOnly;
Matthias@233
   240
        getAnimationState().setAnimationState(STATE_READONLY, readOnly);
Matthias@233
   241
    }
Matthias@233
   242
mam@0
   243
    public void insertText(String str) {
Matthias@233
   244
        if(!readOnly) {
Matthias@233
   245
            boolean update = false;
Matthias@233
   246
            if(hasSelection()) {
Matthias@233
   247
                deleteSelection();
Matthias@233
   248
                update = true;
Matthias@233
   249
            }
Matthias@233
   250
            int insertLength = Math.min(str.length(), maxTextLength - editBuffer.length());
Matthias@233
   251
            if(insertLength > 0) {
Matthias@233
   252
                editBuffer.insert(cursorPos, str, 0, insertLength);
Matthias@233
   253
                cursorPos += insertLength;
Matthias@233
   254
                update = true;
Matthias@233
   255
            }
Matthias@233
   256
            if(update) {
Matthias@233
   257
                updateText();
Matthias@233
   258
            }
mam@0
   259
        }
mam@0
   260
    }
mam@0
   261
mam@0
   262
    public void pasteFromClipboard() {
mam@0
   263
        String cbText = Clipboard.getClipboard();
mam@0
   264
        if(cbText != null) {
mam@0
   265
            cbText = TextUtil.stripNewLines(cbText);
mam@0
   266
            insertText(cbText);
mam@0
   267
        }
mam@0
   268
    }
mam@0
   269
mam@0
   270
    public void copyToClipboard() {
mam@0
   271
        String text;
mam@0
   272
        if(hasSelection()) {
mam@0
   273
            text = getSelectedText();
mam@0
   274
        } else {
mam@0
   275
            text = getText();
mam@0
   276
        }
Matthias@71
   277
        if(isPasswordMasking()) {
Matthias@71
   278
            text = TextUtil.createString(passwordChar, text.length());
Matthias@71
   279
        }
Matthias@71
   280
        Clipboard.setClipboard(text);
Matthias@71
   281
    }
Matthias@71
   282
Matthias@71
   283
    public void cutToClipboard() {
Matthias@71
   284
        String text;
Matthias@71
   285
        if(hasSelection()) {
Matthias@71
   286
            text = getSelectedText();
Matthias@233
   287
            if(!readOnly) {
Matthias@233
   288
                deleteSelection();
Matthias@233
   289
                updateText();
Matthias@233
   290
            }
Matthias@71
   291
        } else {
Matthias@71
   292
            text = getText();
Matthias@233
   293
            if(!readOnly) {
Matthias@233
   294
                setText("");
Matthias@233
   295
            }
Matthias@71
   296
        }
Matthias@71
   297
        if(isPasswordMasking()) {
Matthias@71
   298
            text = TextUtil.createString(passwordChar, text.length());
Matthias@71
   299
        }
mam@0
   300
        Clipboard.setClipboard(text);
mam@0
   301
    }
mam@0
   302
mam@0
   303
    public int getMaxTextLength() {
mam@0
   304
        return maxTextLength;
mam@0
   305
    }
mam@0
   306
mam@0
   307
    public void setMaxTextLength(int maxTextLength) {
mam@0
   308
        this.maxTextLength = maxTextLength;
mam@0
   309
    }
mam@0
   310
mam@0
   311
    @Override
mam@0
   312
    protected void applyTheme(ThemeInfo themeInfo) {
mam@0
   313
        super.applyTheme(themeInfo);
Matthias@313
   314
        applayThemeEditField(themeInfo);
Matthias@313
   315
    }
Matthias@313
   316
Matthias@313
   317
    protected void applayThemeEditField(ThemeInfo themeInfo) {
mam@0
   318
        cursorImage = themeInfo.getImage("cursor");
mam@0
   319
        selectionImage = themeInfo.getImage("selection");
Matthias@270
   320
        autoCompletionHeight = themeInfo.getParameter("autocompletion-height", 100);
Matthias@313
   321
        columns = themeInfo.getParameter("columns", 5);
mam@0
   322
        setPasswordChar((char)themeInfo.getParameter("passwordChar", '*'));
mam@0
   323
        setErrorMessage(errorMsg);  // update color
mam@0
   324
    }
mam@0
   325
mam@0
   326
    @Override
mam@0
   327
    protected void layout() {
Matthias@270
   328
        layoutChildFullInnerArea(textRenderer);
Matthias@237
   329
        checkTextWidth();
Matthias@270
   330
        if(autoCompletionWindow != null) {
Matthias@270
   331
            layoutAutocompletionWindow();
Matthias@270
   332
        }
Matthias@352
   333
        if(errorInfoWindow != null) {
Matthias@352
   334
            layoutErrorInfoWindow();
Matthias@352
   335
        }
mam@0
   336
    }
Matthias@270
   337
Matthias@270
   338
    private void layoutAutocompletionWindow() {
Matthias@270
   339
        autoCompletionWindow.setPosition(getX(), getBottom());
Matthias@270
   340
        autoCompletionWindow.setSize(getWidth(), autoCompletionHeight);
Matthias@270
   341
    }
Matthias@270
   342
Matthias@8
   343
    private int computeInnerWidth() {
Matthias@313
   344
        if(columns > 0) {
Matthias@313
   345
            Font font = textRenderer.getFont();
Matthias@313
   346
            if(font != null) {
Matthias@313
   347
                return font.computeTextWidth("X")*columns;
Matthias@313
   348
            }
Matthias@8
   349
        }
Matthias@8
   350
        return 0;
Matthias@8
   351
    }
Matthias@8
   352
Matthias@8
   353
    private int computeInnerHeight() {
Matthias@8
   354
        Font font = textRenderer.getFont();
Matthias@8
   355
        if(font != null) {
Matthias@8
   356
            return font.getLineHeight();
Matthias@8
   357
        }
Matthias@8
   358
        return 0;
Matthias@8
   359
    }
Matthias@8
   360
Matthias@8
   361
    @Override
Matthias@8
   362
    public int getMinWidth() {
Matthias@8
   363
        int minWidth = super.getMinWidth();
Matthias@8
   364
        minWidth = Math.max(minWidth, computeInnerWidth() + getBorderHorizontal());
Matthias@8
   365
        return minWidth;
Matthias@8
   366
    }
Matthias@8
   367
Matthias@8
   368
    @Override
Matthias@8
   369
    public int getMinHeight() {
Matthias@8
   370
        int minHeight = super.getMinHeight();
Matthias@8
   371
        minHeight = Math.max(minHeight, computeInnerHeight() + getBorderVertical());
Matthias@8
   372
        return minHeight;
Matthias@8
   373
    }
mam@0
   374
mam@0
   375
    @Override
Matthias@42
   376
    public int getPreferredInnerWidth() {
Matthias@8
   377
        return computeInnerWidth();
mam@0
   378
    }
mam@0
   379
mam@0
   380
    @Override
Matthias@42
   381
    public int getPreferredInnerHeight() {
Matthias@8
   382
        return computeInnerHeight();
mam@0
   383
    }
mam@0
   384
mam@0
   385
    public void setErrorMessage(Object errorMsg) {
Matthias@110
   386
        getAnimationState().setAnimationState(STATE_ERROR, errorMsg != null);
mam@0
   387
        if(this.errorMsg != errorMsg) {
mam@0
   388
            this.errorMsg = errorMsg;
mam@0
   389
            GUI gui = getGUI();
mam@0
   390
            if(gui != null) {
mam@0
   391
                gui.requestToolTipUpdate(this);
mam@0
   392
            }
mam@0
   393
        }
Matthias@352
   394
        if(errorMsg != null) {
Matthias@352
   395
            if(hasKeyboardFocus()) {
Matthias@352
   396
                openErrorInfoWindow();
Matthias@352
   397
            }
Matthias@352
   398
        } else if(errorInfoWindow != null) {
Matthias@352
   399
            errorInfoWindow.closeInfo();
Matthias@352
   400
        }
mam@0
   401
    }
mam@0
   402
mam@0
   403
    @Override
mam@0
   404
    public Object getTooltipContent() {
mam@0
   405
        if(errorMsg != null) {
mam@0
   406
            return errorMsg;
mam@0
   407
        }
Matthias@237
   408
        Object tooltip = super.getTooltipContent();
Matthias@238
   409
        if(tooltip == null && !isPasswordMasking() && textLongerThenWidget) {
Matthias@237
   410
            tooltip = getText();
Matthias@237
   411
        }
Matthias@237
   412
        return tooltip;
mam@0
   413
    }
mam@0
   414
Matthias@270
   415
    public void setAutoCompletionWindow(InfoWindow window, boolean wantKeys) {
Matthias@270
   416
        if(window == null) {
Matthias@270
   417
            if(autoCompletionWindow != null) {
Matthias@270
   418
                autoCompletionWindow.closeInfo();
Matthias@270
   419
                autoCompletionWindow = null;
Matthias@270
   420
            }
Matthias@270
   421
            autoCompletionWantKeys = false;
Matthias@270
   422
        } else {
Matthias@270
   423
            autoCompletionWindow = window;
Matthias@270
   424
            autoCompletionWantKeys = wantKeys;
Matthias@270
   425
            if(autoCompletionWindow.openInfo()) {
Matthias@270
   426
                layoutAutocompletionWindow();
Matthias@270
   427
            }
Matthias@270
   428
        }
Matthias@270
   429
    }
Matthias@270
   430
mam@0
   431
    @Override
mam@0
   432
    public boolean handleEvent(Event evt) {
mam@0
   433
        boolean selectPressed = (evt.getModifiers() & Event.MODIFIER_SHIFT) != 0;
mam@0
   434
Matthias@358
   435
        if(evt.isMouseEvent()) {
Matthias@358
   436
            boolean hover = (evt.getType() != Event.Type.MOUSE_EXITED) && isMouseInside(evt);
Matthias@358
   437
            getAnimationState().setAnimationState(STATE_HOVER, hover);
Matthias@358
   438
        }
Matthias@358
   439
mam@0
   440
        if(evt.isMouseDragEvent()) {
mam@0
   441
            if(evt.getType() == Event.Type.MOUSE_DRAGED &&
mam@0
   442
                    (evt.getModifiers() & Event.MODIFIER_LBUTTON) != 0) {
mam@0
   443
                int newPos = textRenderer.getCursorPosFromMouse(evt.getMouseX());
mam@0
   444
                setCursorPos(newPos, true);
mam@0
   445
            }
mam@0
   446
            return true;
mam@0
   447
        }
mam@0
   448
Matthias@135
   449
        if(super.handleEvent(evt)) {
Matthias@135
   450
            return true;
Matthias@135
   451
        }
Matthias@135
   452
Matthias@270
   453
        if(evt.isKeyEvent() && autoCompletionWantKeys) {
Matthias@270
   454
            if(autoCompletionWindow.handleEvent(evt)) {
Matthias@270
   455
                return true;
Matthias@270
   456
            }
Matthias@270
   457
        }
Matthias@270
   458
        
mam@0
   459
        switch (evt.getType()) {
mam@0
   460
        case KEY_PRESSED:
mam@0
   461
            switch (evt.getKeyCode()) {
mam@0
   462
            case Keyboard.KEY_BACK:
mam@0
   463
                deletePrev();
mam@0
   464
                return true;
mam@0
   465
            case Keyboard.KEY_DELETE:
mam@0
   466
                deleteNext();
mam@0
   467
                return true;
mam@0
   468
            case Keyboard.KEY_RETURN:
mam@0
   469
            case Keyboard.KEY_ESCAPE:
mam@0
   470
                doCallback(evt.getKeyCode());
mam@0
   471
                return true;
mam@0
   472
            case Keyboard.KEY_HOME:
mam@0
   473
                setCursorPos(0, selectPressed);
mam@0
   474
                return true;
mam@0
   475
            case Keyboard.KEY_END:
mam@0
   476
                setCursorPos(editBuffer.length(), selectPressed);
mam@0
   477
                return true;
mam@0
   478
            case Keyboard.KEY_LEFT:
mam@0
   479
                moveCursor(-1, selectPressed);
mam@0
   480
                return true;
mam@0
   481
            case Keyboard.KEY_RIGHT:
mam@0
   482
                moveCursor(+1, selectPressed);
mam@0
   483
                return true;
Matthias@270
   484
            case Keyboard.KEY_UP:
Matthias@270
   485
            case Keyboard.KEY_DOWN:
Matthias@270
   486
                if(!autoCompletionWantKeys && autoCompletionWindow != null) {
Matthias@270
   487
                    return autoCompletionWindow.handleEvent(evt);
Matthias@270
   488
                }
Matthias@270
   489
                return false;
Matthias@124
   490
            default:
Matthias@124
   491
                if(evt.hasKeyChar()) {
Matthias@124
   492
                    insertChar(evt.getKeyChar());
Matthias@124
   493
                    return true;
Matthias@124
   494
                }
Matthias@234
   495
                return false;
mam@0
   496
            }
Matthias@234
   497
Matthias@234
   498
        case KEY_RELEASED:
Matthias@234
   499
            switch (evt.getKeyCode()) {
Matthias@234
   500
            case Keyboard.KEY_BACK:
Matthias@234
   501
            case Keyboard.KEY_DELETE:
Matthias@234
   502
            case Keyboard.KEY_RETURN:
Matthias@234
   503
            case Keyboard.KEY_ESCAPE:
Matthias@234
   504
            case Keyboard.KEY_HOME:
Matthias@234
   505
            case Keyboard.KEY_END:
Matthias@234
   506
            case Keyboard.KEY_LEFT:
Matthias@234
   507
            case Keyboard.KEY_RIGHT:
Matthias@234
   508
                return true;
Matthias@270
   509
            case Keyboard.KEY_UP:
Matthias@270
   510
            case Keyboard.KEY_DOWN:
Matthias@270
   511
                if(!autoCompletionWantKeys && autoCompletionWindow != null) {
Matthias@270
   512
                    return autoCompletionWindow.handleEvent(evt);
Matthias@270
   513
                }
Matthias@270
   514
                return false;
Matthias@234
   515
            default:
Matthias@234
   516
                return evt.hasKeyChar();
Matthias@234
   517
            }
mam@0
   518
mam@0
   519
        case MOUSE_BTNUP:
mam@0
   520
            if(evt.getMouseButton() == Event.MOUSE_RBUTTON && isMouseInside(evt)) {
mam@0
   521
                showPopupMenu(evt);
mam@0
   522
                return true;
mam@0
   523
            }
mam@0
   524
            break;
mam@0
   525
mam@0
   526
        case MOUSE_BTNDOWN:
mam@0
   527
            if(evt.getMouseButton() == Event.MOUSE_LBUTTON && isMouseInside(evt)) {
mam@0
   528
                int newPos = textRenderer.getCursorPosFromMouse(evt.getMouseX());
mam@0
   529
                setCursorPos(newPos, selectPressed);
mam@0
   530
                return true;
mam@0
   531
            }
mam@0
   532
            break;
Matthias@50
   533
Matthias@50
   534
        case MOUSE_CLICKED:
Matthias@50
   535
            if(evt.getMouseClickCount() == 2) {
Matthias@50
   536
                int newPos = textRenderer.getCursorPosFromMouse(evt.getMouseX());
Matthias@50
   537
                selectWordFromMouse(newPos);
Matthias@50
   538
                return true;
Matthias@50
   539
            }
Matthias@50
   540
            if(evt.getMouseClickCount() == 3) {
Matthias@50
   541
                selectAll();
Matthias@50
   542
                return true;
Matthias@50
   543
            }
Matthias@50
   544
            break;
Matthias@387
   545
Matthias@387
   546
        case MOUSE_WHEEL:
Matthias@387
   547
            return false;
mam@0
   548
        }
mam@0
   549
mam@0
   550
        return evt.isMouseEvent();
mam@0
   551
    }
mam@0
   552
mam@0
   553
    protected void showPopupMenu(Event evt) {
mam@0
   554
        if(popupMenu == null) {
mam@0
   555
            popupMenu = createPopupMenu();
mam@0
   556
        }
mam@0
   557
        if(popupMenu != null) {
mam@0
   558
            popupMenu.showPopup(evt.getMouseX(), evt.getMouseY());
mam@0
   559
        }
mam@0
   560
    }
mam@0
   561
mam@0
   562
    protected PopupMenu createPopupMenu() {
Matthias@71
   563
        Button btnCut = new Button("cut");
Matthias@71
   564
        btnCut.addCallback(new Runnable() {
Matthias@71
   565
            public void run() {
Matthias@71
   566
                cutToClipboard();
Matthias@71
   567
            }
Matthias@71
   568
        });
Matthias@71
   569
mam@0
   570
        Button btnCopy = new Button("copy");
mam@0
   571
        btnCopy.addCallback(new Runnable() {
mam@0
   572
            public void run() {
mam@0
   573
                copyToClipboard();
mam@0
   574
            }
mam@0
   575
        });
mam@0
   576
Matthias@71
   577
        Button btnPaste = new Button("paste");
Matthias@71
   578
        btnPaste.addCallback(new Runnable() {
Matthias@71
   579
            public void run() {
Matthias@71
   580
                pasteFromClipboard();
Matthias@71
   581
            }
Matthias@71
   582
        });
Matthias@71
   583
mam@0
   584
        Button btnClear = new Button("clear");
mam@0
   585
        btnClear.addCallback(new Runnable() {
mam@0
   586
            public void run() {
Matthias@233
   587
                if(!isReadOnly()) {
Matthias@233
   588
                    setText("");
Matthias@233
   589
                }
mam@0
   590
            }
mam@0
   591
        });
mam@0
   592
mam@0
   593
        PopupMenu menu = new PopupMenu(this);
Matthias@71
   594
        menu.add(btnCut);
mam@0
   595
        menu.add(btnCopy);
Matthias@71
   596
        menu.add(btnPaste);
Matthias@71
   597
        menu.addSpacer();
mam@0
   598
        menu.add(btnClear);
mam@0
   599
        return menu;
mam@0
   600
    }
mam@0
   601
mam@0
   602
    private void updateText() {
Matthias@59
   603
        if(model != null) {
Matthias@59
   604
            model.setValue(getText());
Matthias@59
   605
        }
Matthias@415
   606
        textRenderer.setCharSequence(passwordMasking != null ? passwordMasking : editBuffer);
Matthias@237
   607
        checkTextWidth();
mam@0
   608
        scrollToCursor(false);
mam@0
   609
        doCallback(Keyboard.KEY_NONE);
mam@0
   610
    }
mam@0
   611
Matthias@237
   612
    private void checkTextWidth() {
Matthias@237
   613
        textLongerThenWidget = textRenderer.getPreferredWidth() > textRenderer.getWidth();
Matthias@237
   614
    }
Matthias@237
   615
mam@0
   616
    protected void moveCursor(int dir, boolean select) {
mam@0
   617
        setCursorPos(cursorPos + dir, select);
mam@0
   618
    }
mam@0
   619
mam@0
   620
    protected void setCursorPos(int pos, boolean select) {
mam@0
   621
        pos = Math.max(0, Math.min(editBuffer.length(), pos));
mam@0
   622
        if(!select) {
mam@0
   623
            selectionStart = pos;
mam@0
   624
            selectionEnd = pos;
mam@0
   625
        }
mam@0
   626
        if(this.cursorPos != pos) {
mam@0
   627
            if(select) {
mam@0
   628
                if(hasSelection()) {
mam@0
   629
                    if(cursorPos == selectionStart) {
mam@0
   630
                        selectionStart = pos;
mam@0
   631
                    } else {
mam@0
   632
                        selectionEnd = pos;
mam@0
   633
                    }
mam@0
   634
                } else {
mam@0
   635
                    selectionStart = cursorPos;
mam@0
   636
                    selectionEnd = pos;
mam@0
   637
                }
mam@0
   638
                if(selectionStart > selectionEnd) {
mam@0
   639
                    int t = selectionStart;
mam@0
   640
                    selectionStart = selectionEnd;
mam@0
   641
                    selectionEnd = t;
mam@0
   642
                }
mam@0
   643
            }
mam@0
   644
mam@0
   645
            this.cursorPos = pos;
mam@0
   646
            scrollToCursor(false);
mam@0
   647
        }
mam@0
   648
    }
mam@0
   649
Matthias@135
   650
    public void selectAll() {
Matthias@50
   651
        selectionStart = 0;
Matthias@50
   652
        selectionEnd = editBuffer.length();
Matthias@50
   653
    }
Matthias@50
   654
Matthias@50
   655
    protected void selectWordFromMouse(int index) {
Matthias@50
   656
        selectionStart = index;
Matthias@50
   657
        selectionEnd = index;
Matthias@50
   658
        while(selectionStart > 0 && !Character.isWhitespace(editBuffer.charAt(selectionStart-1))) {
Matthias@50
   659
            selectionStart--;
Matthias@50
   660
        }
Matthias@50
   661
        while(selectionEnd < editBuffer.length() && !Character.isWhitespace(editBuffer.charAt(selectionEnd))) {
Matthias@50
   662
            selectionEnd++;
Matthias@50
   663
        }
Matthias@50
   664
    }
Matthias@50
   665
mam@0
   666
    protected void scrollToCursor(boolean force) {
mam@0
   667
        int xpos = textRenderer.computeRelativeCursorPositionX(cursorPos);
mam@0
   668
        int renderWidth = textRenderer.getWidth() - 5;
mam@0
   669
        if(xpos < scrollPos + 5) {
mam@0
   670
            scrollPos = Math.max(0, xpos - 5);
mam@0
   671
        } else if(force || xpos - scrollPos > renderWidth) {
mam@0
   672
            scrollPos = Math.max(0, xpos - renderWidth);
mam@0
   673
        }
mam@0
   674
    }
mam@0
   675
    
mam@0
   676
    protected void insertChar(char ch) {
mam@0
   677
        // don't add control characters
Matthias@233
   678
        if(!readOnly && !Character.isISOControl(ch)) {
mam@0
   679
            boolean update = false;
mam@0
   680
            if(hasSelection()) {
mam@0
   681
                deleteSelection();
mam@0
   682
                update = true;
mam@0
   683
            }
mam@0
   684
            if(editBuffer.length() < maxTextLength) {
mam@0
   685
                editBuffer.insert(cursorPos, ch);
mam@0
   686
                cursorPos++;
mam@0
   687
                update = true;
mam@0
   688
            }
mam@0
   689
            if(update) {
mam@0
   690
                updateText();
mam@0
   691
            }
mam@0
   692
        }
mam@0
   693
    }
mam@0
   694
mam@0
   695
    protected void deletePrev() {
Matthias@233
   696
        if(!readOnly) {
Matthias@233
   697
            if(hasSelection()) {
Matthias@233
   698
                deleteSelection();
Matthias@233
   699
                updateText();
Matthias@233
   700
            } else if(cursorPos > 0) {
Matthias@233
   701
                --cursorPos;
Matthias@233
   702
                deleteNext();
Matthias@233
   703
            }
mam@0
   704
        }
mam@0
   705
    }
mam@0
   706
mam@0
   707
    protected void deleteNext() {
Matthias@233
   708
        if(!readOnly) {
Matthias@233
   709
            if(hasSelection()) {
Matthias@233
   710
                deleteSelection();
Matthias@233
   711
                updateText();
Matthias@233
   712
            } else if(cursorPos < editBuffer.length()) {
Matthias@233
   713
                editBuffer.deleteCharAt(cursorPos);
Matthias@233
   714
                updateText();
Matthias@233
   715
            }
mam@0
   716
        }
mam@0
   717
    }
mam@0
   718
mam@0
   719
    protected void deleteSelection() {
mam@0
   720
        editBuffer.delete(selectionStart, selectionEnd);
mam@0
   721
        selectionEnd = selectionStart;
mam@0
   722
        setCursorPos(selectionStart, false);
mam@0
   723
    }
mam@0
   724
Matthias@59
   725
    protected void modelChanged() {
Matthias@59
   726
        String modelText = model.getValue();
Matthias@59
   727
        if(editBuffer.length() != modelText.length() || !getText().equals(modelText)) {
Matthias@59
   728
            setText(modelText);
Matthias@59
   729
        }
Matthias@59
   730
    }
Matthias@59
   731
Matthias@71
   732
    protected boolean hasFocusOrPopup() {
Matthias@71
   733
        return hasKeyboardFocus() || hasOpenPopups();
Matthias@71
   734
    }
Matthias@71
   735
    
Matthias@67
   736
    @Override
Matthias@67
   737
    protected void paintOverlay(GUI gui) {
Matthias@71
   738
        if(cursorImage != null && hasFocusOrPopup()) {
Matthias@67
   739
            int xpos = textRenderer.lastTextX + textRenderer.computeRelativeCursorPositionX(cursorPos);
Matthias@67
   740
            cursorImage.draw(getAnimationState(), xpos, textRenderer.computeTextY(),
Matthias@67
   741
                    cursorImage.getWidth(), textRenderer.getFont().getLineHeight());
Matthias@67
   742
        }
Matthias@67
   743
        super.paintOverlay(gui);
Matthias@67
   744
    }
Matthias@67
   745
Matthias@352
   746
    private void openErrorInfoWindow() {
Matthias@352
   747
        if(autoCompletionWindow == null || !autoCompletionWindow.isOpen()) {
Matthias@352
   748
            if(errorInfoWindow == null) {
Matthias@352
   749
                errorInfoLabel = new Label();
Matthias@352
   750
                errorInfoWindow = new InfoWindow(this);
Matthias@352
   751
                errorInfoWindow.setTheme("editfield-errorinfowindow");
Matthias@352
   752
                errorInfoWindow.add(errorInfoLabel);
Matthias@352
   753
            }
Matthias@352
   754
            errorInfoLabel.setText(errorMsg.toString());
Matthias@352
   755
            errorInfoWindow.openInfo();
Matthias@352
   756
            layoutErrorInfoWindow();
Matthias@352
   757
        }
Matthias@352
   758
    }
Matthias@352
   759
Matthias@352
   760
    private void layoutErrorInfoWindow() {
Matthias@352
   761
        errorInfoWindow.setSize(getWidth(), errorInfoWindow.getPreferredHeight());
Matthias@352
   762
        errorInfoWindow.setPosition(getX(), getBottom());
Matthias@352
   763
    }
Matthias@352
   764
Matthias@352
   765
    @Override
Matthias@352
   766
    protected void keyboardFocusGained() {
Matthias@352
   767
        if(errorMsg != null) {
Matthias@352
   768
            openErrorInfoWindow();
Matthias@352
   769
        }
Matthias@352
   770
    }
Matthias@352
   771
Matthias@352
   772
    @Override
Matthias@352
   773
    protected void keyboardFocusLost() {
Matthias@352
   774
        super.keyboardFocusLost();
Matthias@352
   775
        if(errorInfoWindow != null) {
Matthias@352
   776
            errorInfoWindow.closeInfo();
Matthias@352
   777
        }
Matthias@352
   778
    }
Matthias@352
   779
Matthias@59
   780
    protected class ModelChangeListener implements Runnable {
Matthias@59
   781
        public void run() {
Matthias@59
   782
            modelChanged();
Matthias@59
   783
        }
Matthias@59
   784
    }
Matthias@59
   785
mam@0
   786
    protected class TextRenderer extends TextWidget {
mam@0
   787
        int lastTextX;
mam@0
   788
mam@0
   789
        protected TextRenderer(AnimationState animState) {
mam@0
   790
            super(animState);
mam@0
   791
        }
mam@0
   792
mam@0
   793
        @Override
mam@0
   794
        protected void paintWidget(GUI gui) {
mam@0
   795
            lastTextX = computeTextX();
Matthias@71
   796
            if(hasSelection() && hasFocusOrPopup()) {
mam@0
   797
                if(selectionImage != null) {
mam@0
   798
                    int xpos0 = lastTextX + computeRelativeCursorPositionX(selectionStart);
mam@0
   799
                    int xpos1 = lastTextX + computeRelativeCursorPositionX(selectionEnd);
mam@0
   800
                    selectionImage.draw(getAnimationState(), xpos0, computeTextY(),
mam@0
   801
                            xpos1 - xpos0, getFont().getLineHeight());
mam@0
   802
                }
Matthias@110
   803
                paintWithSelection(getAnimationState(), selectionStart, selectionEnd);
Matthias@110
   804
            } else {
Matthias@110
   805
                paintLabelText(getAnimationState());
mam@0
   806
            }
mam@0
   807
        }
mam@0
   808
mam@0
   809
        @Override
mam@0
   810
        protected void sizeChanged() {
mam@0
   811
            scrollToCursor(true);
mam@0
   812
        }
mam@0
   813
mam@0
   814
        @Override
mam@0
   815
        protected int computeTextX() {
Matthias@125
   816
            if(hasFocusOrPopup()) {
mam@0
   817
                return getInnerX() - scrollPos;
mam@0
   818
            } else {
mam@0
   819
                return getInnerX();
mam@0
   820
            }
mam@0
   821
        }
mam@0
   822
mam@0
   823
        protected int getCursorPosFromMouse(int x) {
mam@0
   824
            if(getFont() != null) {
mam@0
   825
                x += getFont().getSpaceWidth() / 2;
mam@0
   826
                return getFont().computeVisibleGlpyhs(
mam@0
   827
                        getText(), 0, editBuffer.length(),
mam@0
   828
                        x - lastTextX);
mam@0
   829
            } else {
mam@0
   830
                return 0;
mam@0
   831
            }
mam@0
   832
        }
mam@0
   833
    }
mam@0
   834
mam@0
   835
    static class PasswordMasker implements CharSequence {
mam@0
   836
        private final CharSequence base;
mam@0
   837
        private final char maskingChar;
mam@0
   838
mam@0
   839
        public PasswordMasker(CharSequence base, char maskingChar) {
mam@0
   840
            this.base = base;
mam@0
   841
            this.maskingChar = maskingChar;
mam@0
   842
        }
mam@0
   843
mam@0
   844
        public int length() {
mam@0
   845
            return base.length();
mam@0
   846
        }
mam@0
   847
mam@0
   848
        public char charAt(int index) {
mam@0
   849
            return maskingChar;
mam@0
   850
        }
mam@0
   851
mam@0
   852
        public CharSequence subSequence(int start, int end) {
mam@0
   853
            throw new UnsupportedOperationException("Not supported.");
mam@0
   854
        }
mam@0
   855
    }
mam@0
   856
mam@0
   857
}