org.executequery.gui.editor.QueryEditorTextPane.java Source code

Java tutorial

Introduction

Here is the source code for org.executequery.gui.editor.QueryEditorTextPane.java

Source

/*
 * QueryEditorTextPane.java
 *
 * Copyright (C) 2002-2015 Takis Diakoumis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.executequery.gui.editor;

import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import javax.swing.text.Utilities;

import org.apache.commons.lang.StringUtils;
import org.executequery.Constants;
import org.executequery.GUIUtilities;
import org.executequery.components.LineNumber;
import org.executequery.gui.UndoableComponent;
import org.executequery.gui.text.SQLTextPane;
import org.executequery.gui.text.TextUndoManager;
import org.executequery.repository.EditorSQLShortcut;
import org.executequery.repository.EditorSQLShortcuts;
import org.executequery.repository.KeywordRepository;
import org.executequery.repository.RepositoryCache;
import org.executequery.sql.SqlMessages;
import org.executequery.util.UserProperties;
import org.underworldlabs.util.MiscUtils;

/**
 * The SQL text area for the Query Editor.
 *
 * @author   Takis Diakoumis
 * @version  $Revision: 1487 $
 * @date     $Date: 2015-08-23 22:21:42 +1000 (Sun, 23 Aug 2015) $
 */
public class QueryEditorTextPane extends SQLTextPane
        implements UndoableComponent, CaretListener, FocusListener, DocumentListener {

    private static final int DEFAULT_CARET_BLINK_RATE = 500;

    private static final Insets INSETS = new Insets(2, 2, 2, 2);

    private static final int DIRECTION_UP = 1;
    private static final int DIRECTION_DOWN = 2;

    /** The editor panel containing this text component */
    private QueryEditorTextPanel editorPanel;

    /** To display line numbers */
    private LineNumber lineBorder;

    /** The text pane's undo manager */
    protected TextUndoManager undoManager;

    private Map<String, EditorSQLShortcut> editorShortcuts;

    public QueryEditorTextPane(QueryEditorTextPanel editorPanel) {

        this.editorPanel = editorPanel;

        try {

            /*
            Action actions[] = getActions();
            Comparator<Action> comparator = new Comparator<Action>() {
              public int compare(Action a1, Action a2) {
            String firstName = (String) a1.getValue(Action.NAME);
            String secondName = (String) a2.getValue(Action.NAME);
            return firstName.compareTo(secondName);
              }
            };
            Arrays.sort(actions, comparator);
                
            int count = actions.length;
            System.out.println("Count: " + count);
            for (int i = 0; i < count; i++) {
                
            System.out.printf("%28s : %s\n",
                    actions[i].getValue(Action.NAME),
                    actions[i].getClass().getName());
            }
            */

            /*
            ActionMap actionMap = getActionMap();
            actionMap.put(DefaultEditorKit.selectWordAction,
                new QueryEditorSelectWordAction(DefaultEditorKit.selectWordAction, false));
                
            // **** ctrl-right
            actionMap.put(DefaultEditorKit.nextWordAction,
                new QueryEditorBeginWordAction(DefaultEditorKit.nextWordAction,
                        actionMap.get(DefaultEditorKit.nextWordAction)));
                
            // **** ctrl-left
            actionMap.put(DefaultEditorKit.previousWordAction,
                new QueryEditorPreviousWordAction(DefaultEditorKit.previousWordAction,
                        actionMap.get(DefaultEditorKit.previousWordAction)));
            */

            init();

        } catch (Exception e) {

            e.printStackTrace();
        }
    }

    // ctrl-left
    static class QueryEditorPreviousWordAction extends TextAction {

        private final Action originalAction;

        QueryEditorPreviousWordAction(String nm, Action originalAction) {

            super(nm);
            this.originalAction = originalAction;
        }

        public void actionPerformed(ActionEvent e) {

            JTextComponent target = getTextComponent(e);

            if (target != null) {

                try {

                    int offset = target.getCaretPosition();
                    int begOffs = Utilities.getWordStart(target, offset);
                    int endOffs = Utilities.getWordEnd(target, offset);

                    int caretPositionInWord = offset - begOffs;
                    Document document = target.getDocument();
                    String wordAtCursor = document.getText(begOffs, (endOffs - begOffs));

                    System.out.println("A - " + wordAtCursor);

                    if (StringUtils.isBlank(wordAtCursor)) {

                        originalAction.actionPerformed(e);
                        return;
                    }

                    if (caretPositionInWord == 0) {

                        if (offset > 0 && !Character.isWhitespace(document.getText(offset - 1, 1).charAt(0))) {

                            System.out.println("B - " + wordAtCursor);
                        }

                        //                        originalAction.actionPerformed(e);
                        //                        return;
                    }

                    String[] strings = StringUtils.splitByCharacterTypeCamelCase(wordAtCursor);
                    if (strings.length == 1) {

                        originalAction.actionPerformed(e);
                        return;
                    }

                    int movingOffset = 0;
                    for (String string : strings) {

                        int length = string.length();
                        movingOffset += length;

                        if (movingOffset >= caretPositionInWord) {

                            begOffs += (movingOffset - length);
                            break;
                        }

                    }

                    target.setCaretPosition(begOffs);

                } catch (BadLocationException bl) {

                    originalAction.actionPerformed(e);
                }

            }

        }

    }

    // ctrl-right
    static class QueryEditorBeginWordAction extends TextAction {

        private final Action originalAction;

        public QueryEditorBeginWordAction(String nm, Action originalAction) {
            super(nm);
            this.originalAction = originalAction;
        }

        public void actionPerformed(ActionEvent e) {

            JTextComponent target = getTextComponent(e);
            if (target != null) {

                try {

                    int offset = target.getCaretPosition();
                    int begOffs = Utilities.getWordStart(target, offset);
                    int endOffs = Utilities.getWordEnd(target, offset);

                    int caretPositionInWord = offset - begOffs;
                    String wordAtCursor = target.getDocument().getText(begOffs, (endOffs - begOffs));

                    if (caretPositionInWord == wordAtCursor.length()) {

                        originalAction.actionPerformed(e);
                        return;
                    }

                    String[] strings = StringUtils.splitByCharacterTypeCamelCase(wordAtCursor);
                    if (strings.length == 1) {

                        originalAction.actionPerformed(e);
                        return;
                    }

                    int movingOffset = 0;
                    for (String string : strings) {

                        int length = string.length();
                        movingOffset += length;

                        if (movingOffset > caretPositionInWord) {

                            if (begOffs > 0) {

                                begOffs += movingOffset;

                            } else {

                                begOffs = movingOffset;
                            }
                            break;
                        }

                    }

                    target.setCaretPosition(begOffs);

                } catch (BadLocationException bl) {

                    UIManager.getLookAndFeel().provideErrorFeedback(target);
                }

            }

        }

    }

    private static final String separatorChars = ".(){}{},:;_-+/<>*&$";

    // word selection start
    static class QueryEditorBeginWordSelectionAction extends TextAction {

        public QueryEditorBeginWordSelectionAction(String nm) {

            super(nm);
        }

        public void actionPerformed(ActionEvent e) {

            JTextComponent target = getTextComponent(e);
            if (target != null) {

                try {

                    int offset = target.getCaretPosition();
                    int begOffs = Utilities.getWordStart(target, offset);
                    int endOffs = Utilities.getWordEnd(target, offset);

                    int wordOffset = offset - begOffs;

                    String wordAtCursor = target.getDocument().getText(begOffs, (endOffs - begOffs));

                    int movingOffset = 0;
                    String[] strings = StringUtils.splitPreserveAllTokens(wordAtCursor, separatorChars, -1);

                    /*
                    if (strings.length == 1) {
                        
                    // use original action
                    }
                    */

                    int count = 0;
                    for (String string : strings) {

                        int length = string.length();
                        movingOffset += length;
                        if (movingOffset > wordOffset) {

                            begOffs += (movingOffset - length);
                            break;
                        }
                        count++;

                    }

                    begOffs += count;
                    target.setCaretPosition(begOffs);

                } catch (BadLocationException bl) {

                    UIManager.getLookAndFeel().provideErrorFeedback(target);
                }

            }

        }

    }

    // word selection end
    static class QueryEditorEndWordSelectionAction extends TextAction {

        public QueryEditorEndWordSelectionAction(String name) {

            super(name);
        }

        public void actionPerformed(ActionEvent e) {

            JTextComponent target = getTextComponent(e);
            if (target != null) {
                try {
                    int offset = target.getCaretPosition();
                    int begOffs = Utilities.getWordStart(target, offset);
                    int endOffs = Utilities.getWordEnd(target, offset);

                    int wordOffset = offset - begOffs;

                    String wordAtCursor = target.getDocument().getText(begOffs, (endOffs - begOffs));

                    int count = 0;
                    int movingOffset = 0;
                    String[] strings = StringUtils.splitPreserveAllTokens(wordAtCursor, separatorChars, -1);
                    for (String string : strings) {

                        int length = string.length();
                        movingOffset += length;
                        if (movingOffset > wordOffset) {

                            endOffs = begOffs + movingOffset;
                            break;
                        }
                        count++;

                    }

                    endOffs += count;
                    target.moveCaretPosition(endOffs);

                } catch (BadLocationException bl) {

                    UIManager.getLookAndFeel().provideErrorFeedback(target);
                }

            }

        }

    }

    static class QueryEditorSelectWordAction extends TextAction {

        private Action start;
        private Action end;

        public QueryEditorSelectWordAction(String nm, boolean select) {
            super(DefaultEditorKit.selectWordAction);
            start = new QueryEditorBeginWordSelectionAction("beginWord");
            end = new QueryEditorEndWordSelectionAction("endWord");
        }

        public void actionPerformed(ActionEvent e) {
            start.actionPerformed(e);
            end.actionPerformed(e);
        }

    }

    private void init() throws Exception {

        setMargin(INSETS);

        if (editorPanel == null) {

            setEditorPreferences();
        }

        // add the line number border and caret listener
        lineBorder = new LineNumber(this);
        addCaretListener(this);

        // undo functionality
        undoManager = new TextUndoManager(this);
        undoManager.setLimit(userProperties().getIntProperty("editor.undo.count"));

        document.addDocumentListener(this);
        addFocusListener(this);

        setDragEnabled(true);
        setRequestFocusEnabled(true);
        setFocusable(true);

        // set the caret
        createCaret();

        // set to insert mode
        document.setInsertMode(SqlMessages.INSERT_MODE);

        editorShortcuts = new HashMap<String, EditorSQLShortcut>();
        loadEditorShortcuts();
    }

    private void createCaret() {

        EditorCaret caret = new EditorCaret();

        int blinkRate = UIManager.getInt("TextPane.caretBlinkRate");
        if (blinkRate > 0) {

            caret.setBlinkRate(blinkRate);

        } else {

            caret.setBlinkRate(DEFAULT_CARET_BLINK_RATE);
        }
        setCaret(caret);
    }

    private UserProperties userProperties() {
        return UserProperties.getInstance();
    }

    public void showLineNumbers(boolean show) {
        lineBorder.getParent().setVisible(show);
    }

    public void disableUpdates(boolean disable) {

        String text = getText();
        if (disable) {

            setDocument(new DefaultStyledDocument());
            setText(text);
            disableCaretUpdate(true);

        } else {

            setDocument(document);
            setText(text);
            disableCaretUpdate(false);
        }

    }

    public void disableCaretUpdate(boolean disable) {

        if (disable) {

            removeCaretListener(this);

        } else {

            boolean hasListener = false;
            CaretListener[] caretListners = getCaretListeners();

            for (int i = 0; i < caretListners.length; i++) {

                if (caretListners[i] == this) {
                    hasListener = true;
                    break;
                }

            }

            if (!hasListener) {
                addCaretListener(this);
                caretUpdate(null);
            }

        }

    }

    /**
     * Clears the undo managers stored edits
     */
    protected void clearEdits() {
        undoManager.discardAllEdits();
    }

    /**
     * Removes (designed to be temporary) listeners attached
     * to this text pane including the caret updates and
     * undo/redo listener objects.
     */
    protected void uninstallListeners() {
        removeCaretListener(this);
        document.removeDocumentListener(this);
    }

    /**
     * Reinstates listeners attached to this text pane
     * including the caret updates and undo/redo listener objects.
     */
    protected void reinstallListeners() {
        addCaretListener(this);
        document.addDocumentListener(this);
    }

    private void loadDummyDocument() {
        setDocument(new DefaultStyledDocument());
    }

    /**
     * Inserts the specified text at the offset.
     *
     * @param offset - the insertion point
     * @param text - the text
     */
    public void insertTextAtOffset(int offset, String text) {
        try {
            fireTextUpdateStarting();
            loadDummyDocument();

            try {
                // clear the contents of we have any
                int length = document.getLength();

                if (offset > length || offset < 0) {
                    offset = 0;
                }

                document.insertString(offset, text, null);

            } catch (BadLocationException e) {
            }

            setDocument(document);

        } finally {

            fireTextUpdateFinished();
            setCaretPosition(offset);
        }
    }

    /**
     * Loads the specified text into a blank 'offscreen' document
     * before switching to the SQL document. This is most effective
     * for very large chunks of text loaded from file or similar.
     */
    public void loadText(String text) {

        try {

            undoManager.reset();
            undoManager.suspend();
            fireTextUpdateStarting();

            // clear the current held edits
            clearEdits();

            // create a dummy document to load the text into
            loadDummyDocument();

            try {
                // clear the contents of we have any
                int length = document.getLength();
                if (length > 0) {

                    // replace the existing text
                    document.replace(0, length, text, null);

                } else {

                    // set the new text
                    document.insertString(0, text, null);
                }

            } catch (BadLocationException e) {
            }

            // reset the SQL document
            setDocument(document);

        } finally {

            fireTextUpdateFinished();
            undoManager.reinstate();
            setCaretPosition(0);
        }

    }

    private void fireTextUpdateStarting() {
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        uninstallListeners();
    }

    private void fireTextUpdateFinished() {
        updateLineBorder();
        reinstallListeners();
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }

    /**
     * Override to update the line border.
     */
    public void setText(String text) {
        super.setText(text);
        updateLineBorder();
    }

    /**
     * Override to return false.
     */
    public boolean isOpaque() {
        return false;
    }

    /**
     * Paints the current line highlight and right-hand margin
     * before a call to the super class.
     *
     * @param g the <code>Graphics</code> object to protect
     */
    public void paintComponent(Graphics g) {

        int height = getHeight();
        int width = getWidth();

        g.setColor(getBackground());
        g.fillRect(0, 0, width, height);

        // paint the current line highlight
        if (QueryEditorSettings.isDisplayLineHighlight()) {
            int currentRow = getCurrentCursorRow();
            g.setColor(QueryEditorSettings.getLineHighlightColour());
            g.fillRect(1, (currentRow * fontHeight) + 2, width - 1, fontHeight);
        }

        // paint the right-hand margin
        if (QueryEditorSettings.isDisplayRightMargin()) {
            int xPosn = fontWidth * QueryEditorSettings.getRightMarginSize();
            g.setColor(QueryEditorSettings.getRightMarginColour());
            g.drawLine(xPosn, 0, xPosn, height);
        }

        try {
            super.paintComponent(g);
        } catch (Exception e) {
        }

    }

    public void setQueryAreaText(String s) {
        setText(s);
    }

    /**
     * Shifts the text at position start to position end left.
     *
     * @param start - the start offset
     * @param end - the end offset
     */
    public void shiftTextLeft(int start, int end) {

        getSQLSyntaxDocument().shiftTabEvent(start, end, false);
    }

    /**
     * Shifts the text at position start to the right.
     * This will usually be the start position of any
     * particular row.
     *
     * @param start - the start offset
     */
    public void shiftTextRight(int offset) {

        insertTextAtOffset(offset, "\t");
    }

    public void insertTextAfter(int after, String text) {
        if (getDocument().getLength() > 0) {
            text = "\n" + text;
        }
        insertTextAtOffset(after, text);
    }

    public JTextPane getQueryArea() {
        return this;
    }

    public JComponent getLineBorder() {
        return lineBorder;
    }

    public void goToRow(int row) {
        int goToRow = getRowPosition(row - 1);
        if (goToRow < 0) {
            GUIUtilities.displayErrorMessage("The line number enterinsertTextAftered is invalid.");
            return;
        }
        setCaretPosition(goToRow);
    }

    protected void setEditorPreferences() {
        // call to super class
        super.setEditorPreferences();
        // set the pane background
        setBackground(QueryEditorSettings.getEditorBackground());
    }

    public void setSQLKeywords(boolean reset) {

        document.setSQLKeywords(keywords().getSQLKeywords(), reset);
    }

    private KeywordRepository keywords() {

        return (KeywordRepository) RepositoryCache.load(KeywordRepository.REPOSITORY_ID);
    }

    public void resetAttributeSets() {
        String text = getText();
        setEditorPreferences();
        document.resetAttributeSets();
        lineBorder.updatePreferences(QueryEditorSettings.getEditorFont());
        lineBorder.repaint();
        setText(text);
    }

    /**
     * Returns the query around the specified (cursor) position.
     *
     * @param the position
     * @return the query around the specified position
     */
    private QueryWithPosition getQueryAt(int position) {

        String text = getText();
        if (MiscUtils.isNull(text)) {

            return new QueryWithPosition(0, 0, 0, Constants.EMPTY);
        }

        char[] chars = text.toCharArray();

        if (position == chars.length) {
            position--;
        }

        int start = -1;
        int end = -1;
        boolean wasSpaceChar = false;

        // determine the start point
        for (int i = position; i >= 0; i--) {

            if (chars[i] == Constants.NEW_LINE_CHAR) {

                if (i == 0 || wasSpaceChar) {

                    break;

                } else if (start != -1) {

                    if (chars[i - 1] == Constants.NEW_LINE_CHAR) {

                        break;

                    } else if (Character.isSpaceChar(chars[i - 1])) {

                        wasSpaceChar = true;
                        i--;
                    }

                }

            } else if (!Character.isSpaceChar(chars[i])) {

                wasSpaceChar = false;
                start = i;
            }

        }

        if (start < 0) { // text not found
            for (int j = 0; j < chars.length; j++) {
                if (!Character.isWhitespace(chars[j])) {
                    start = j;
                    break;
                }
            }
        }

        // determine the end point
        for (int i = start; i < chars.length; i++) {

            if (chars[i] == Constants.NEW_LINE_CHAR) {

                if (i == chars.length - 1 || wasSpaceChar) {
                    if (end == -1) {
                        end = i;
                    }
                    break;
                } else if (end != -1) {
                    if (chars[i + 1] == Constants.NEW_LINE_CHAR) {
                        break;
                    } else if (Character.isSpaceChar(chars[i + 1])) {
                        wasSpaceChar = true;
                        i++;
                    }
                }

            } else if (!Character.isSpaceChar(chars[i])) {
                end = i;
                wasSpaceChar = false;
            }
        }

        //Log.debug("start: " + start + " end: " + end);

        String query = text.substring(start, end + 1);
        //Log.debug(query);

        if ((MiscUtils.isNull(query) && start != 0)) { // || start == end) {

            return getQueryAt(start);
        }

        return new QueryWithPosition(position, start, end + 1, query);
    }

    // ----------------------------------------
    // DocumentListener implementation
    // ----------------------------------------

    /**
     * Does nothing.
     */
    public void changedUpdate(DocumentEvent e) {
    }

    /**
     * Notifies the parent QueryPanel that the text content
     * has changed and resets the line number border panel.
     *
     * @param the event object
     */
    public void insertUpdate(DocumentEvent e) {
        editorPanel.setContentChanged(true);
        lineBorder.resetExecutingLine();
    }

    /**
     * Notifies the parent QueryPanel that the text content
     * has changed and resets the line number border panel.
     *
     * @param the event object
     */
    public void removeUpdate(DocumentEvent e) {
        insertUpdate(e);
    }

    // ----------------------------------------

    /**
     * Resets the executing line within the line
     * number border panel.
     */
    public void resetExecutingLine() {
        lineBorder.resetExecutingLine();
    }

    public QueryWithPosition getQueryAtCursor() {

        return getQueryAt(getCaretPosition());
    }

    public String getWordEndingAtCursor() {

        return getWordEndingAt(getCaretPosition());
    }

    public String getCompleteWordEndingAtCursor() {

        String text = getText();
        if (MiscUtils.isNull(text)) {

            return Constants.EMPTY;
        }

        int start = indexOfWordStartFromIndex(getCaretPosition());
        int end = indexOfWordEndFromIndex();

        if (start < 0) {

            start = 0;
        }

        if (end < 0) {

            end = getText().length();
        }

        return text.substring(start, end).trim();
    }

    private int indexOfWordStartFromIndex(int index) {

        int start = -1;
        int end = index;

        char[] chars = getText().toCharArray();
        for (int i = end - 1; i >= 0; i--) {

            if (!Character.isLetterOrDigit(chars[i]) && chars[i] != '_' && chars[i] != '.') {

                start = i;
                break;
            }

        }

        return start;
    }

    private int indexOfWordEndFromIndex() {

        int start = getCaretPosition();
        char[] chars = getText().toCharArray();

        for (int i = start; i < chars.length; i++) {

            if (Character.isWhitespace(chars[i])) {

                return i;
            }

        }

        return -1;
    }

    private String getWordEndingAt(int position) {

        String text = getText();

        if (MiscUtils.isNull(text)) {

            return Constants.EMPTY;
        }

        char[] chars = text.toCharArray();

        int start = -1;
        int end = position;

        for (int i = end - 1; i >= 0; i--) {

            if (!Character.isLetterOrDigit(chars[i]) && chars[i] != '_' && chars[i] != '.') {

                start = i;
                break;
            }

        }

        if (start < 0) {

            start = 0;
        }

        return text.substring(start, end).trim();
    }

    protected void setExecutingQuery(String query) {

        int index = getText().indexOf(query);
        if (query.charAt(0) == Constants.NEW_LINE_CHAR) {

            index++;
        }

        lineBorder.setExecutingLine(getRowAt(index));
        lineBorder.repaint();
    }

    public String getTextAtRow(int rowNumber) {

        Element line = getElementMap().getElement(rowNumber);

        int startOffset = line.getStartOffset();
        int endOffset = line.getEndOffset();
        try {

            return getText(startOffset, (endOffset - startOffset));

        } catch (BadLocationException e) {

            e.printStackTrace();
            return null;
        }
    }

    /**
     * Overrides <code>processKeyEvent</code> to additional process events.
     */
    protected void processKeyEvent(KeyEvent e) {

        if (e.getID() == KeyEvent.KEY_PRESSED) {

            int keyCode = e.getKeyCode();

            // add the processing for SHIFT-TAB
            if (e.isShiftDown() && keyCode == KeyEvent.VK_TAB) {

                //                int currentPosition = getCurrentPosition();
                int selectionStart = getSelectionStart();
                int selectionEnd = getSelectionEnd();

                if (selectionStart == selectionEnd) {

                    int start = getCurrentRowStart();
                    int end = getCurrentRowEnd();

                    shiftTextLeft(start, end);

                    /*
                    int newPosition = currentPosition - QueryEditorSettings.getTabSize();
                    int currentRowPosition = getCurrentRowStart();
                        
                    if (!isAtStartOfRow()) {
                        
                    if (newPosition < 0) {
                        
                        setCaretPosition(0);
                        
                    } else if (newPosition < currentRowPosition) {
                        
                        setCaretPosition(currentRowPosition);
                        
                    } else {
                        
                        setCaretPosition(newPosition);
                    }
                        
                    }
                    */

                } else {

                    document.shiftTabEvent(selectionStart, selectionEnd);
                }

            } else if (keyCode == KeyEvent.VK_INSERT && e.getModifiers() == 0) {

                // toggle insert mode on the document

                int insertMode = document.getInsertMode();
                if (insertMode == SqlMessages.INSERT_MODE) {

                    document.setInsertMode(SqlMessages.OVERWRITE_MODE);
                    editorPanel.getStatusBar().setInsertionMode("OVR");

                } else {

                    document.setInsertMode(SqlMessages.INSERT_MODE);
                    editorPanel.getStatusBar().setInsertionMode("INS");
                }

                ((EditorCaret) getCaret()).modeChanged();

            } else if (keyCode == KeyEvent.VK_SPACE) {

                checkForShortcutText();
            }

        }

        super.processKeyEvent(e);
        updateLineBorder();
    }

    private void checkForShortcutText() {

        int index = getCaretPosition();
        String word = getWordEndingAt(index).toUpperCase();

        word = removeBracesAtStart(word);
        if (editorShortcuts.containsKey(word)) {

            String text = editorShortcuts.get(word).getQuery();
            try {
                document.replace(index - word.length(), word.length(), text, null);
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
        }

    }

    private String removeBracesAtStart(String word) {

        int index = 0;
        String braces = "{}[]()";
        char[] chars = word.toCharArray();

        for (char c : chars) {

            if (braces.indexOf(c) != -1) {

                index++;

            } else {

                break;
            }

        }

        return word.substring(index);
    }

    public void editorShortcutsUpdated() {

        editorShortcuts.clear();
        loadEditorShortcuts();
    }

    private void loadEditorShortcuts() {

        List<EditorSQLShortcut> shortcuts = EditorSQLShortcuts.getInstance().getEditorShortcuts();
        for (EditorSQLShortcut editorSQLShortcut : shortcuts) {

            editorShortcuts.put(editorSQLShortcut.getShortcut(), editorSQLShortcut);
        }
    }

    /** the last element count for line border updates */
    private int lastElementCount;

    /**
     * Updates the line border values.
     */
    private void updateLineBorder() {
        int elementCount = document.getDefaultRootElement().getElementCount();
        if (elementCount != lastElementCount) {
            lineBorder.setRowCount(elementCount);
            lastElementCount = elementCount;
        }
    }

    /**
     * Returns true if an undo operation would be
     * successful now, false otherwise.
     */
    protected boolean canUndo() {
        return undoManager.canUndo();
    }

    /**
     * Executes the undo action.
     */
    public void undo() {
        undoManager.undo();
        updateLineBorder();
    }

    /**
     * Executes the redo action.
     */
    public void redo() {
        undoManager.redo();
        updateLineBorder();
    }

    // ----------------------------------------
    // FocusListener implementation
    // ----------------------------------------

    /**
     * Updates the state of undo/redo ona focus gain.
     */
    public void focusGained(FocusEvent e) {
        if (e.getSource() != this && editorPanel != null) {
            editorPanel.focusGained();
        }
    }

    /**
     * Updates the state of undo/redo on a focus lost.
     */
    public void focusLost(FocusEvent e) {
        if (editorPanel != null) {
            editorPanel.focusLost();
        }
    }

    // ----------------------------------------

    private int currentRow = 0;
    private int currentPosition = 0;

    /**
     * Returns the row number at the specified position.
     *
     * @param position - the position
     */
    protected int getRowAt(int position) {
        Element map = getElementMap();
        return map.getElementIndex(position);
    }

    /**
     * Called when the caret position is updated.
     *
     * @param e the caret event
     */
    public void caretUpdate(CaretEvent ce) {

        super.caretUpdate(ce);
        currentPosition = getCaretPosition();

        Element map = getElementMap();
        int row = map.getElementIndex(currentPosition);

        if (currentRow != row) {
            currentRow = row;
            //lineBorder.setRowCount(map.getElementCount());
        }

        Element lineElem = map.getElement(row);
        int col = currentPosition - lineElem.getStartOffset();
        editorPanel.getStatusBar().setCaretPosition(row + 1, col + 1);

        repaint();
    }

    /*
    private String getSelectedTextOrCurrentRow() {
        
    try {
        
        String text = getSelectedText();
        if (StringUtils.isBlank(text) ||
                ((getSelectionStart() != getCurrentRowStart()
                        || getSelectionEnd() != getCurrentRowEnd()) && !text.contains("\n") ) ) {
        
            text = getCurrentRowText();
        }
        
        return StringUtils.trim(text);
        
    } catch (BadLocationException e) {}
        
    return "";
    }
    */

    public void moveSelectionUp() {

        try {

            int start = getStartOffsetAtSelectionOrCursor();
            if (start == 0) {

                return;
            }
            int end = getEndOffsetAtSelectionOrCursor();

            int previousRow = getRowAt(start) - 1;
            String previousRowText = getTextAtRow(previousRow);
            if (previousRowText == null) {

                return;
            }

            String textToMove = getText(start, (end - start));
            String textToInsert = textToMove + previousRowText;

            if (getRowAt(end) == getRowAt(end - 1)) {

                textToInsert = StringUtils.removeEnd(textToInsert, "\n");

            }

            int insertLength = textToInsert.length();
            int insertAt = getRowStartOffset(previousRow);
            if (insertAt + insertLength > getDocument().getLength()) {

                insertLength--;
            }

            getDocument().remove(insertAt, insertLength);
            insertTextAtOffset(insertAt, textToInsert);

            setSelectionStart(insertAt);
            setSelectionEnd(insertAt + textToMove.length());

        } catch (BadLocationException e) {

            return;
        }
    }

    public void moveSelectionDown() {

        try {

            int start = getStartOffsetAtSelectionOrCursor();
            int end = getEndOffsetAtSelectionOrCursor();

            int nextRow = getRowAt(end);
            String nextRowText = getTextAtRow(nextRow);
            if (nextRowText == null) {

                return;
            }

            String textToMove = StringUtils.removeEnd(getText(start, (end - start)), "\n");
            String textToInsert = nextRowText + textToMove;

            int insertLength = textToInsert.length();
            if (start + insertLength > getDocument().getLength()) {

                insertLength--;
            }

            getDocument().remove(start, insertLength);
            insertTextAtOffset(start, textToInsert);

            int selectionStart = start + nextRowText.length();
            setSelectionStart(selectionStart);
            setSelectionEnd(selectionStart + textToMove.length() + 1);

        } catch (BadLocationException e) {

            return;
        }
    }

    public void duplicateTextUp() {

        duplicateSelectionOrRowToOffset(DIRECTION_UP);
    }

    public void duplicateTextDown() {

        duplicateSelectionOrRowToOffset(DIRECTION_DOWN);
    }

    private int getStartOffsetAtSelectionOrCursor() {

        int row = currentRow;
        if (hasTextSelected()) {

            row = getRowAt(getSelectionStart());
        }
        return getRowStartOffset(row);
    }

    private int getEndOffsetAtSelectionOrCursor() {

        int row = getRowAt(getSelectionStart());
        if (hasTextSelected()) {

            row = getRowAt(getSelectionEnd() - 1);

        }
        return getRowEndOffset(row);
    }

    private int getLastRow() {

        return getRowAt(getElementMap().getEndOffset());
    }

    private void duplicateSelectionOrRowToOffset(int direction) {

        try {

            int start = getStartOffsetAtSelectionOrCursor();
            int end = getEndOffsetAtSelectionOrCursor();

            int offset = 0;
            int selectionOffset = 0;

            String insertText = getText(start, (end - start));
            insertText = StringUtils.removeEnd(insertText, "\n");

            if (direction == DIRECTION_UP) {

                insertText += "\n";
                offset = start;
                selectionOffset = offset;

            } else {

                insertText = "\n" + insertText;
                offset = end - 1;
                selectionOffset = offset + 1;

                if (StringUtils.isWhitespace(insertText) && getLastRow() == getRowAt(end)) {

                    return;
                }

            }

            insertTextAtOffset(offset, insertText);
            setCaretPosition(offset + insertText.length());
            moveCaretPosition(selectionOffset);

        } catch (Exception e) {
        }

    }

    private boolean hasTextSelected() {

        return getSelectionStart() != getSelectionEnd();
    }

    /*
    private String getCurrentRowText() throws BadLocationException {
        
    int start = getCurrentRowStart();
    int end = getCurrentRowEnd();
        
    return document.getText(start, end - start);
    }
    */

    protected boolean isAtStartOfRow() {
        return currentPosition == getRowPosition(currentRow);
    }

    /**
     * Returns the start offset of the current row.
     *
     * @return the current row start offset
     */
    protected int getCurrentRowStart() {
        return getElementMap().getElement(currentRow).getStartOffset();
    }

    /**
     * Returns the end offset of the current row.
     *
     * @return the current row end offset
     */
    protected int getCurrentRowEnd() {
        return getElementMap().getElement(currentRow).getEndOffset();
    }

    /**
     * Returns the start offset of the specified row.
     *
     * @param row - the row
     * @return the start offset of row
     */
    protected int getRowStartOffset(int row) {
        try {
            return getElementMap().getElement(row).getStartOffset();
        } catch (Exception e) { // where row passed is dumb value
            return -1;
        }
    }

    /**
     * Returns the end offset of the specified row.
     *
     * @param row - the row
     * @return the end offset of row
     */
    protected int getRowEndOffset(int row) {
        try {
            return getElementMap().getElement(row).getEndOffset();
        } catch (Exception e) { // where row passed is dumb value
            return -1;
        }
    }

    /**
     * Returns the start offset of the specified row.
     *
     * @param row - a row in the editor
     * @return the start offset of row
     */
    protected int getRowPosition(int row) {
        try {
            return getElementMap().getElement(row).getStartOffset();
        } catch (NullPointerException nullExc) { // TODO: WTF????
            return -1;
        }
    }

    /**
     * Returns the document's root element
     */
    protected Element getElementMap() {
        return getDocument().getDefaultRootElement();
    }

    /**
     * Returns the current caret position.
     */
    protected int getCurrentPosition() {
        return currentPosition;
    }

    /**
     * Returns the row number of the current cursor position.
     *
     * @return the current caret row number
     */
    protected int getCurrentCursorRow() {
        return currentRow;
    }

    class EditorCaret extends DefaultCaret {

        void modeChanged() {
            repaint();
        }

        public void paint(Graphics g) {
            if (document.getInsertMode() == SqlMessages.INSERT_MODE) {
                super.paint(g);
                return;
            }
            JTextComponent comp = getComponent();

            char c;
            int dot = getDot();
            Rectangle r = null;
            try {
                r = comp.modelToView(dot);
                if (r == null) {
                    return;
                }
                c = comp.getText(dot, 1).charAt(0);
            } catch (BadLocationException e) {
                return;
            }

            // erase provious caret
            if ((x != r.x) || (y != r.y)) {
                repaint();
                x = r.x;
                y = r.y;
                height = r.height;
            }

            g.setColor(comp.getCaretColor());
            g.setXORMode(comp.getBackground());

            width = g.getFontMetrics().charWidth(c);
            if (c == '\t' || c == '\n') {
                width = g.getFontMetrics().charWidth('W');
            }

            if (isVisible()) {
                g.fillRect(r.x, r.y, width, r.height);
            }

        }

    }

}