/** @see javax.swing.TransferHandler#importData(javax.swing.TransferHandler.TransferSupport) */
    @Override
    public boolean importData(TransferSupport info) {
      Transferable t = info.getTransferable();
      String data;
      try {
        data = (String) t.getTransferData(DataFlavor.stringFlavor);
      } catch (Exception e) {
        return false;
      }
      UndoPatch up = new UndoPatch();
      if (info.isDrop()) {
        Point p = mouseToPoint(info.getDropLocation().getDropPoint(), true);
        caret.row = p.y;
        caret.col = p.x;
        sel.deselect(false);
        sel.type = ST.NORM;
        up.startRow = caret.row;
        up.reconstruct(p.y, Math.min(code.size() - 1, p.y + sel.getInsertRipple(data) - 1));
      }
      int er = 0;
      if (data.length() > 0 && data.charAt(data.length() - 1) == 0) {
        er = Math.max(1, sel.insertRect(data.substring(0, data.length() - 1))) - 1;
      } else {
        sel.insert(data);
      }
      up.realize(caret.row + er);
      storeUndo(up, OPT.PASTE);
      repaint();
      return true;
    }
  }

  // ===============================================================================================
  // ===== Be Scrollable ===========================================================================
  // ===============================================================================================

  /** @see javax.swing.Scrollable#getPreferredScrollableViewportSize() */
  @Override
  public Dimension getPreferredScrollableViewportSize() {
    return new Dimension(320, 240);
  }

  /** @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int) */
  @Override
  public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
    switch (orientation) {
      case SwingConstants.VERTICAL:
        return getFontMetrics(getFont()).getHeight();
      case SwingConstants.HORIZONTAL:
        return monoAdvance;
      default:
        throw new IllegalArgumentException("Invalid orientation: " + orientation); //$NON-NLS-1$
    }
  }

  /** @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int) */
  @Override
  public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
    switch (orientation) {
      case SwingConstants.VERTICAL:
        return visibleRect.height;
      case SwingConstants.HORIZONTAL:
        return visibleRect.width;
      default:
        throw new IllegalArgumentException("Invalid orientation: " + orientation); //$NON-NLS-1$
    }
  }

  /** @see javax.swing.Scrollable#getScrollableTracksViewportHeight() */
  @Override
  public boolean getScrollableTracksViewportHeight() {
    return false;
  }

  /** The scrollable should not track the viewport width. */
  @Override
  public boolean getScrollableTracksViewportWidth() {
    return false;
  }

  /** Handle a resize. */
  void fireResize() {
    Container a = getParent();
    Dimension ps = getMinimumSize();
    if (a != null) {
      int w = a.getWidth(), h = a.getHeight();
      ps.width = Math.max(ps.width, w);
      ps.height = Math.max(ps.height, h);
    }
    setPreferredSize(ps);
    setSize(ps);
  }

  /** Listen to parent component */
  @Override
  public void componentResized(ComponentEvent e) {
    fireResize();
  }

  /** Repaint when covered partially by another window. */
  @Override
  public void componentHidden(ComponentEvent e) {
    repaint();
  }

  /** @see java.awt.event.ComponentListener#componentMoved(java.awt.event.ComponentEvent) */
  @Override
  public void componentMoved(ComponentEvent e) {
    repaint();
  }

  /** @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent) */
  @Override
  public void componentShown(ComponentEvent e) {
    repaint();
  }

  /** Be a clipboard owner and do not give a fuck. */
  @Override
  public void lostOwnership(Clipboard arg0, Transferable arg1) {
    // WHOGIVESAFUCK.jpg
  }

  // ===============================================================================================
  // ===== Mark Matching Brackets ==================================================================
  // ===============================================================================================

  /**
   * @author Josh Ventura
   *         The status of a match; there are three match states.
   */
  static enum MatchState {
    /** No attempt is presently being made to match anything. */
    NOT_MATCHING,
    /** A match was attempted, but none was found. */
    NO_MATCH,
    /** A match was successfully located. */
    MATCHING
  }

  /**
   * @author Josh Ventura
   *         A highlighter to mark pairs of matching brackets.
   */
  class BracketHighlighter implements Highlighter, CaretListener {
    /** The state of our match. */
    MatchState matching;
    /** The line on which the match was found. */
    int matchLine;
    /** The column at which the match was found. */
    int matchPos;

    /** Paint a box around each bracket in the match. */
    @Override
    public void paint(Graphics g, Insets i, CodeMetrics gm, int line_start, int line_end) {
      Color c = g.getColor();

      // TODO: Make sure we haven't deleted a selection of code that fires a bracket repaint on a
      // line that was deleted. Current check suffices to fix the exception. - Robert
      if (matchLine < line_end) {
        if (matching == MatchState.MATCHING) {
          g.setColor(matchingCharColor);
          g.drawRect(line_wid_at(matchLine, matchPos), matchLine * lineHeight, monoAdvance,
              lineHeight);
        } else if (matching == MatchState.NO_MATCH) {
          g.setColor(noMatchingCharColor);
          g.fillRect(line_wid_at(matchLine, matchPos), matchLine * lineHeight, monoAdvance,
              lineHeight);
        }
      }
      g.setColor(c);
    }

    /**
     * @param m
     *        The current BracketMatch instance.
     * @param x
     *        The column of the most recent match candidate.
     * @param y
     *        The row of the most recent match candidate.
     * @return True if the match candidate is indeed the match to the original bracket.
     */
    private boolean matchFound(BracketMatch m, int x, int y) {
      if (--m.count <= 0) {
        matchLine = y;
        matchPos = x;
        matching = MatchState.MATCHING;
        return true;
      }
      return false;
    }

    /**
     * @author Josh Ventura
     *         Class to match various brackets; parentheses, square brackets, braces.
     */
    class BracketMatch {
      /** The character of the bracket to be matched. */
      char match;
      /** The character of the matching bracket; the opposite-side bracket. */
      char opposite;
      /** The number of nested brackets during this matching process. */
      short count;

      /**
       * @param m
       *        The character of the bracket to be matched.
       * @param o
       *        The character of the matching bracket; the opposite-side bracket.
       * @param c
       *        The number of nested brackets during this matching process.
       */
      public BracketMatch(char m, char o, short c) {
        match = m;
        opposite = o;
        count = c;
      }
    }

    /**
     * @param row
     *        The row of the bracket to match.
     * @param col
     *        The column of the bracket to match.
     * @param match
     *        The BracketMatch instance for this match attempt.
     */
    private void findMatchForward(int row, int col, BracketMatch match) {
      int y = row;
      int blockType = 0;
      StringBuilder sb = code.getsb(y);

      // Figure out what kind of block we're in, if any.
      ArrayList<TokenMarkerInfo> tmall = marker.getStyles(code.get(y));

      int offset;
      for (offset = 0; offset < tmall.size(); offset++) {
        TokenMarkerInfo tm = tmall.get(offset);
        if (col < tm.startPos) {
          break; // The blocks have skipped us.
        }
        if (col >= tm.startPos && col < tm.endPos) {
          blockType = tm.blockHash;
          break;
        }
      }
      if (subFindMatchForward(match, sb, tmall, offset, col, blockType, y)) {
        return;
      }

      for (y++; y < code.size(); y++) {
        tmall = marker.getStyles(code.get(y));
        if (subFindMatchForward(match, code.getsb(y), tmall, 0, 0, blockType, y)) {
          return;
        }
      }
    }

    /**
     * @param match
     *        The BracketMatch class representing this search.
     * @param sb
     *        The StringBuilder of the line to search.The position from which to continue searching.
     * @param tmall
     *        All marker tokens for this line.
     * @param offset
     *        The marker token index from which to continue searching.
     * @param spos
     *        The position from which to continue searching.
     * @param blockType
     *        The type of block in which we are interested.
     * @param y
     *        Our row number.
     * @return True if the match was found in this line, false otherwise.
     *         Info about the match will be contained in the match parameter.
     */
    private boolean subFindMatchForward(BracketMatch match, StringBuilder sb,
        ArrayList<TokenMarkerInfo> tmall, int offset, int spos, int blockType, int y) {
      int pos = spos;
      for (int i = offset; i < tmall.size(); i++) {
        TokenMarkerInfo tm = tmall.get(i);
        if (blockType == 0) {
          for (; pos < tm.startPos; pos++) {
            // Check outside this block's range
            if (sb.charAt(pos) == match.match) {
              if (matchFound(match, pos, y)) {
                return true;
              }
            } else if (sb.charAt(pos) == match.opposite) {
              match.count++;
            }
          }
        }
        if (blockType == tmall.get(i).blockHash) {
          // same type
          for (pos = Math.max(spos, tm.startPos); pos < tm.endPos; pos++) {
            // Check inside it
            if (sb.charAt(pos) == match.match) {
              if (matchFound(match, pos, y)) {
                return true;
              }
            } else if (sb.charAt(pos) == match.opposite) {
              match.count++;
            }
          }
        }
        pos = tm.endPos;
      }
      return false;
    }

    /**
     * @param row
     *        The row of the bracket to match.
     * @param col
     *        The column of the bracket to match.
     * @param match
     *        The BracketMatch instance for this match attempt.
     */
    private void findMatchBackward(int row, int col, BracketMatch match) {
      int y = row;
      int blockType = 0;
      StringBuilder sb = code.getsb(y);

      // Figure out what kind of block we're in, if any.
      ArrayList<TokenMarkerInfo> tmall = marker.getStyles(code.get(y));

      int offset;
      for (offset = 0; offset < tmall.size(); offset++) {
        TokenMarkerInfo tm = tmall.get(offset);
        if (col < tm.startPos) {
          break; // The blocks have skipped us.
        }
        if (col >= tm.startPos && col < tm.endPos) {
          blockType = tm.blockHash;
          break;
        }
      }
      if (subFindMatchBackward(match, sb, tmall, offset, caret.col, blockType, y)) {
        return;
      }

      for (y--; y >= 0; y--) {
        tmall = marker.getStyles(code.get(y));
        if (subFindMatchBackward(match, code.getsb(y), tmall, tmall.size() - 1,
            code.getsb(y).length(), blockType, y)) {
          return;
        }
      }
    }

    /**
     * @param match
     *        The BracketMatch class representing this search.
     * @param sb
     *        The StringBuilder of the line to search.The position from which to continue searching.
     * @param tmall
     *        All marker tokens for this line.
     * @param offset
     *        The marker token index from which to continue searching.
     * @param spos
     *        The position from which to continue searching.
     * @param blockType
     *        The type of block in which we are interested.
     * @param y
     *        Our row number.
     * @return True if the match was found in this line, false otherwise.
     *         Info about the match will be contained in the match parameter.
     */
    private boolean subFindMatchBackward(BracketMatch match, StringBuilder sb,
        ArrayList<TokenMarkerInfo> tmall, int offset, int spos, int blockType, int y) {
      int pos = spos;
      int i = offset;
      TokenMarkerInfo tm = tmall.get(i);
      for (;;) {
        if (blockType == tmall.get(i).blockHash) {
          // same type
          for (pos = Math.min(spos, tm.endPos - 1); pos >= tm.startPos; pos--) {
            // Check inside it
            if (sb.charAt(pos) == match.match) {
              if (matchFound(match, pos, y)) {
                return true;
              }
            } else if (sb.charAt(pos) == match.opposite) {
              match.count++;
            }
          }
        }
        if (i > 0) {
          tm = tmall.get(--i);
          if (blockType == 0) {
            for (pos = tm.startPos - 1; pos >= tm.endPos; pos--) {
              // Check outside this block's range
              if (sb.charAt(pos) == match.match) {
                if (matchFound(match, pos, y)) {
                  return true;
                }
              } else if (sb.charAt(pos) == match.opposite) {
                match.count++;
              }
            }
          }
        } else {
          if (blockType == 0) {
            for (pos = tm.startPos - 1; pos >= 0; pos--) {
              // Check outside this block's range
              if (sb.charAt(pos) == match.match) {
                if (matchFound(match, pos, y)) {
                  return true;
                }
              } else if (sb.charAt(pos) == match.opposite) {
                match.count++;
              }
            }
          }
          break;
        }
      }
      return false;
    }

    /** Callback for when the caret position changes. */
    @Override
    public void caretUpdate(CaretEvent ce) {
      matching = MatchState.NOT_MATCHING;
      StringBuilder sb = code.getsb(caret.row);
      String start = "([{", end = ")]}"; //$NON-NLS-1$ //$NON-NLS-2$
      for (int x : new int[] { caret.col - 1, caret.col }) {
        if (x >= 0 && x < sb.length()) {
          char c = sb.charAt(x);
          int p = start.indexOf(c);
          if (p != -1) {
            findMatchForward(caret.row, x, new BracketMatch(end.charAt(p), start.charAt(p),
                (short) 0));
            return;
          }
          p = end.indexOf(c);
          if (p != -1) {
            findMatchBackward(caret.row, x, new BracketMatch(start.charAt(p), end.charAt(p),
                (short) 0));
            return;
          }
        }
      }
    }
  }

  // ===============================================================================================
  // ===== Undo/Redo functionality =================================================================
  // ===============================================================================================

  /**
   * A class of a dozen types an UndoPatch can have.
   *
   * @author josh
   */
  static final class OPT {
    /** The patch is a one-of-a-kind that isn't worth its own constant. */
    public static final int OTHER = 0;
    /** The patch contains something the user typed. */
    public static final int TYPED = 1;
    /** The patch is from backspacing over something. */
    public static final int BACKSPACE = 2;
    /** The patch is from deleting something. */
    public static final int DELETE = 3;
    /**
     * The patch is from typing a space; 'space' gets its
     * own type to allow changing merge behavior around it
     */
    public static final int SPACE = 4;
    /** The patch is from a newline insertion. */
    public static final int ENTER = 5;
    /** The patch is from the 'paste' function. */
    public static final int PASTE = 6;
    /** The patch is from changing line indentation. */
    public static final int INDENT = 7;
    /** The patch is from the 'duplicate line' function. */
    public static final int DUPLICATE = 8;
    /** The patch is from a find-replace replace. */
    public static final int REPLACE = 9;
    /** The patch is from the 'swap lines' function. */
    public static final int SWAP = 10;
    /** The patch is from the 'unswap lines' function. */
    public static final int UNSWAP = 11;
  }

  /**
   * A class for storing two patches of code used in the
   * Undo and Redo mechanisms. An UndoPatch contains sufficient
   * information to be undone and applied any number of times
   * during its lifecycle, with perfect accuracy assuming each
   * patch is applied in the same order it was created and reverted
   * in the opposite. (And was, of course, constructed properly.)
   *
   * @author josh
   */
  class UndoPatch {
    /** The type of this UndoPatch; a member of {@link OPT} */
    int opTag;
    /**
     * The text stored before the code was modified, hereafter
     * referred to as the "pre-patch text."
     */
    Line[] oldText;
    /**
     * The text as it stood after the modifications that sparked
     * the creation of this UndoPatch, hereafter just "patch text."
     */
    Line[] patchText;
    /**
     * The index of the row that begins both segments of text.
     * In other words, both oldText and patchText must start at the same
     * line.
     */
    int startRow;

    /** Caret/selection information from before the action in this undo item. */
    CaretData cbefore = new CaretData();
    /** Caret/selection information for immediately after the action in this undo item. */
    CaretData cafter = new CaretData();

    /**
     * Storage class for grabbing caret data and later replacing it.
     */
    class CaretData {
      /** The stored caret.column. */
      public int ccol;
      /** The stored caret.row. */
      public int crow;
      /** The stored sel.column. */
      public int scol;
      /** The stored sel.row. */
      public int srow;
      /** The stored sel.type */
      ST stype;

      /**
       * Grab the current caret indexes for use later.
       */
      public void grab() {
        ccol = caret.col;
        crow = caret.row;
        scol = sel.col;
        srow = sel.row;
        stype = sel.type;
      }

      /**
       * Replace previously grabbed caret data, modifying this.caret.
       */
      public void replace() {
        caret.col = ccol;
        caret.row = crow;
        sel.col = scol;
        sel.row = srow;
        sel.type = stype;
      }

      /**
       * Copy data from another instance.
       *
       * @param cfrom
       *        The CaretData class to copy from.
       */
      public void copy(CaretData cfrom) {
        ccol = cfrom.ccol;
        crow = cfrom.crow;
        scol = cfrom.scol;
        srow = cfrom.srow;
        stype = cfrom.stype;
      }
    }

    /**
     * Construct a complete, finalized UndoPatch manually.
     *
     * @param t
     *        The pre-patch code.
     * @param ot
     *        The patch code.
     * @param sr
     *        The index of the starting row of the two codes.
     */
    UndoPatch(Line[] t, Line[] ot, int sr) {
      oldText = ot;
      patchText = t;
      startRow = sr;
    }

    /**
     * Prefixes a line to the stored pre-patch code, decrementing
     * the recorded startRow to account for the change.
     *
     * @param ln
     *        The line to prefix to our stored pre-patch code.
     */
    public void prefix_row(Line ln) {
      startRow--;
      Line[] ancient = oldText;
      oldText = new Line[oldText.length + 1];
      oldText[0] = ln;
      for (int i = 0; i < ancient.length; i++) {
        oldText[i + 1] = ancient[i];
      }
    }

    /**
     * A convenience constructor; same as calling the other overload as
     * UndoPatch(<caret.row,sel.row>.sort).
     */
    UndoPatch() {
      this(Math.min(caret.row, sel.row), Math.max(caret.row, sel.row));
    }

    /**
     * Construct a new UndoPatch, copying in pre-patch code from {@link code} in the given row
     * interval.
     *
     * @param startRow
     * @param endRow
     */
    UndoPatch(int startRow, int endRow) {
      final int lc = endRow - startRow + 1;
      oldText = new Line[lc];
      for (int i = 0; i < lc; i++) {
        oldText[i] = new Line(code.get(startRow + i));
      }
      this.startRow = startRow;
      cbefore.grab();
    }

    /**
     * Reconstruct the stored pre-patch text by copying from JoshText.code in
     * the given row range.
     *
     * @param newStartRow
     *        The index of the first row to copy.
     * @param newEndRow
     *        The index of the last row to copy.
     */
    public void reconstruct(int newStartRow, int newEndRow) {
      final int lc = newEndRow - newStartRow + 1;
      oldText = new Line[lc];
      for (int i = 0; i < lc; i++) {
        oldText[i] = new Line(code.get(startRow + i));
      }
      startRow = newStartRow;
    }

    /**
     * Populates the patchText member by copying data stored in this.code
     * from the previously given starting line this.startRow to the newly
     * specified end row.
     *
     * @param endRow
     *        The row at which to stop copying the patch text.
     */
    public void realize(int endRow) {
      fireLineChange(startRow, endRow);
      final int lc = endRow - startRow + 1;
      patchText = new Line[lc];
      for (int i = 0; i < lc; i++) {
        patchText[i] = new Line(code.get(startRow + i));
      }
      cafter.grab();
    }
  }

  /**
   * An array of all available UndoPatches to be reverted (as in Undo) or re-applied (as in Redo).
   */
  private final ArrayList<UndoPatch> undoPatches = new ArrayList<UndoPatch>();
  /**
   * A control variable that determines whether a new {@link UndoPatch} can be merged with an old if
   * they are compatible.
   */
  private boolean undoCanMerge = true;
  /**
   * Our position in {@link undoPatches}.
   */
  private int patchIndex = 0;

  /**
   * Check whether we have available undos.
   */
  public boolean canUndo() {
    return patchIndex > 0 && undoPatches.size() > 0;
  }

  /**
   * Check whether we have available redos.
   */
  public boolean canRedo() {
    return patchIndex < undoPatches.size();
  }

  /**
   * Undo the most recently stored UndoPatch.
   * The patch itself is not moved; instead, patchIndex is decremented.
   */
  public void undo() {
    if (patchIndex == 0) {
      return;
    }
    UndoPatch p = undoPatches.get(--patchIndex);
    // Reverse patch
    int prow;
    for (prow = 0; prow < p.patchText.length; prow++) {
      if (prow >= p.oldText.length) {
        for (int da = p.patchText.length - prow; da > 0; da--) {
          code.remove(p.startRow + prow);
        }
        break;
      }
      code.set(p.startRow + prow, new Line(p.oldText[prow]));
    }
    while (prow < p.oldText.length) {
      code.add(p.startRow + prow, new Line(p.oldText[prow]));
      prow++;
    }
    p.cbefore.replace();
    fireLineChange(p.startRow, p.startRow + p.oldText.length);
    repaint();
  }

  /**
   * Re-apply the UndoPatch that has most recently been undone.
   * If no previous UndoPatch has been reverted, return without error.
   */
  public void redo() {
    if (patchIndex >= undoPatches.size()) {
      return;
    }
    UndoPatch p = undoPatches.get(patchIndex++);
    // Perform patch
    int prow;
    for (prow = 0; prow < p.oldText.length; prow++) {
      if (prow >= p.patchText.length) {
        for (int da = p.oldText.length - prow; da > 0; da--) {
          code.remove(p.startRow + prow);
        }
        break;
      }
      code.set(p.startRow + prow, new Line(p.patchText[prow]));
    }
    while (prow < p.patchText.length) {
      code.add(p.startRow + prow, new Line(p.patchText[prow]));
      prow++;
    }
    p.cafter.replace();
    fireLineChange(p.startRow, p.startRow + p.oldText.length);
    repaint();
  }

  /**
   * Store an UndoPatch so we can undo it later.
   *
   * @param undo
   *        The UndoPatch to store.
   * @param patchType
   *        The type of the patch, as a constant from OPT.
   */
  public void storeUndo(UndoPatch undo, int patchType) {
    undo.opTag = patchType;
    while (patchIndex < undoPatches.size()) {
      undoPatches.remove(undoPatches.size() - 1);
    }
    if (!undoCanMerge || patchIndex == 0
        || !undoCompatible(undoPatches.get(patchIndex - 1), undo)) {
      undoPatches.add(undo);
      undoCanMerge = true;
      patchIndex++;
    } else {
      undoMerge(undo, undoPatches.get(patchIndex - 1));
    }
  }

  /**
   * Merge two undo patches into one, assuming the patches have the
   * same starting line index and line count.
   *
   * @param merge
   *        The new UndoPatch to merge in.
   * @param into
   *        The old UndoPatch into which we will merge the new one.
   */
  private static void undoMerge(UndoPatch merge, UndoPatch into) {
    into.patchText = merge.patchText;
    into.cafter.copy(merge.cafter);
    into.opTag = merge.opTag;
  }

  /**
   * Utility function to check if two UndoPatches are ideal candidates for
   * merging.
   *
   * @param up1
   *        Some UndoPatch, which will be tested for merge sanity.
   * @param up2
   *        Some other UndoPatch against which to test the first for
   *        compatibility.
   * @return Whether, given the two undo patches, they should be merged
   *         instead of pushing the new one.
   * @note It is immaterial which UndoPatch is newer.
   */
  private static boolean undoCompatible(UndoPatch up1, UndoPatch up2) {
    if ((up1.opTag != up2.opTag && up2.opTag != OPT.SPACE) || up1.startRow != up2.startRow) {
      return false;
    }
    if (up1.oldText.length != up1.patchText.length || up1.oldText.length != up2.oldText.length
        || up2.patchText.length != up2.patchText.length) {
      return false;
    }
    return true;
  }

  /** @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) */
  @Override
  public void focusGained(FocusEvent arg0) {
    FindDialog.getInstance().selectedJoshText = this;
  }

  /** @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) */
  @Override
  public void focusLost(FocusEvent arg0) { // Unused
  }