#########################################################################
""" scintilla.py -- Python wrapper for Gtk Scintilla text editor

Copyright (c) 1999-2000, Archaeopteryx Software, Inc.  All rights reserved.

Written by Stephan R.A. Deibel (sdeibel@archaeopteryx.com) and
John Ehresman (jpe@archaeopteryx.com)

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Notes:

- This could have benefitted from few entry points in scintillamodule.c
  but so far I haven't been motivated sufficiently to redesign.

-------------
Modifications:

$Log: scintilla.py,v $
Revision 1.20  2000/11/27 23:08:54  sdeibel
Added support for indentation guides

Revision 1.19  2000/11/27 22:24:05  sdeibel
Partial impl of using indent guides

Revision 1.18  2000/11/26 22:59:38  jpe
Added move_caret_inside_view method.

Revision 1.17  2000/11/07 17:34:51  sdeibel
Fixed ability to reset lexers multiple times

Revision 1.16  2000/10/19 22:35:53  sdeibel
Better busy cursor framework

Revision 1.15  2000/09/11 22:23:20  jpe
Exposed eol mode flag get/set functions

Revision 1.14  2000/07/12 18:55:00  jpe
Added get/set autocomplete_cancel_at_start methods.

Revision 1.13  2000/06/16 03:10:44  sdeibel
Added support for scintilla's indentation API

Revision 1.12  2000/06/16 02:29:51  sdeibel
Added support for calltips

Revision 1.11  2000/06/16 01:47:57  sdeibel
Added support for indicators

Revision 1.10  2000/06/15 22:08:08  sdeibel
Added support for scintilla underlining, charsets, and turning on/off
the horizontal scrollbar

Revision 1.9  2000/06/15 21:15:36  sdeibel
Updated to support all lexers in scintilla 1.26 and removed old colorizing
code and added some usage docs

Revision 1.8  2000/06/06 05:19:28  sdeibel
Seperated out the new vars manager code and added a vars detail
panel to debugger window for expansion of long values.

Revision 1.7  2000/06/05 22:56:38  sdeibel
Start at a new variables display impl; currently packaged as a way
to expand whole structures or long strings

Revision 1.6  2000/04/19 22:38:02  jpe
Exposed autocompletion interface and add Python lex state constants

Revision 1.5  2000/03/31 20:51:10  jpe
Added support for using internal Scintilla lexers with document objects

Revision 1.4  2000/03/30 17:58:12  sdeibel
Added support for new signals

Revision 1.3  2000/03/29 22:44:28  jpe
Exposed brace match related functions

Revision 1.2  2000/03/29 18:54:56  jpe
Implemented setting default keywords and styles for built-in lexers.  The
defaults are kept in def_lexer_info.py, which is generated from a SciTE
properties file.

Revision 1.1  2000/03/28 00:39:36  jpe
Initial check in of restructured sources.

Revision 1.41  2000/03/20 21:14:27  sdeibel
Fixed missing return values for search functions

Revision 1.40  2000/03/17 17:01:40  sdeibel
Added ability to turn off default popup menu and changed rest of
gtk signal handlers to avoid blocking user-installed handlers from
seeing the signal

Revision 1.39  2000/03/16 00:48:08  sdeibel
Removed junk function

Revision 1.38  2000/03/15 22:30:55  sdeibel
Added lines_on_screen call and code to expose that call from within
Scintilla

Revision 1.37  2000/03/15 21:40:01  sdeibel
Fixed typo in can_paste

Revision 1.36  2000/03/08 23:14:01  jpe
Changed name of extension module from _scintilla to libscint.

Revision 1.35  2000/02/16 20:48:23  sdeibel
Fixed typo

Revision 1.34  2000/02/11 20:19:10  sdeibel
Added utility for key event conversion

Revision 1.33  2000/02/11 06:28:01  sdeibel
Fixed bugs in misc function support

Revision 1.32  2000/02/10 01:31:34  sdeibel
Added missing deletion and special key insertion commands

Revision 1.31  2000/02/04 17:52:43  sdeibel
Misc fixes to macro support: now route popup's copy/paste/etc through
editor wndproc, removed a few non-macroable messages from support,
and added return value for aborting macro on error.

Revision 1.30  2000/02/04 06:30:28  sdeibel
Implemented macros; intermediate checkin; not fully debugged

Revision 1.29  2000/02/04 00:55:26  jpe
Fixes for forgotton &'s in calls to PyArg_ParseTuple

Revision 1.28  2000/02/03 22:57:03  jpe
Initial checkin of Document object wrapping.  Only minimal testinng
has been done.

Revision 1.27  2000/02/03 22:37:04  sdeibel
Fixed insane relocatable search implementation to cleaner approach that works

Revision 1.26  2000/02/03 02:39:57  sdeibel
Added caret positioning policy support and find-and-select message

Revision 1.25  2000/02/02 14:57:55  sdeibel
Updated copyright dates

Revision 1.24  2000/01/27 07:07:24  sdeibel
Fixed minor API bugs/omissions

Revision 1.23  2000/01/26 21:12:26  sdeibel
Updated to scintilla 1.2 release

Revision 1.22  2000/01/21 04:00:54  sdeibel
Added support for notification when user changes the selected
area of text

Revision 1.21  2000/01/20 23:41:16  sdeibel
Added support for assigning command keys

Revision 1.20  2000/01/20 16:29:42  sdeibel
Added cursor movement API

Revision 1.19  2000/01/14 08:38:46  sdeibel
Added brace matching support, fixed EM_GETLINE bug, misc other added
functions.  Now v. 115py4.

Revision 1.18  2000/01/12 19:55:44  sdeibel
Release 115py3

Revision 1.17  2000/01/12 19:40:45  sdeibel
Added low-level text change notification support

Revision 1.16  1999/11/23 21:31:55  sdeibel
Wrote the styling interface and added misc missing calls.  Not
yet debugged.

Revision 1.15  1999/11/23 18:40:43  sdeibel
Notify and command/change signals implemented.  Also converted
to EX form of messages to avoid limiting doc size.

Revision 1.14  1999/11/19 23:33:49  sdeibel
Started special signal interface for handling scintilla-specific
callbacks via gtk_connect commands

Revision 1.13  1999/11/19 18:11:27  sdeibel
Prepared 1.1.3py1 dist

Revision 1.12  1999/11/19 17:56:07  sdeibel
Updated to version 1.1.3.  Changes from 1.1.3 dist are in ScintillaGTK::
ScrollText (fixed optimized blit scrolling), and commented out calls to
SendMessage in KeyWords.cxx.  Changes to python module include change in
get_position_at_point so it returns (pos, line) tuple, added
get_linelength_total call (which gets line length to \r or \n instead of
to line wrap point), and added comments about semantics to python file.

Revision 1.11  1999/11/18 17:48:45  sdeibel
Prepared scintilla dist 1.1.1py1

[truncated]

"""
#########################################################################

import GTK
import GDK
import gtk
import _gtk
import libscint
import string
import def_lexer_info
from sci_names import *

#-----------------------------------------------------------------------

class Scintilla(gtk.GtkWidget):
  get_type = libscint.scintilla_get_type

  # Constructor
  def __init__(self, _obj=None):

    # Init inherited
    gtk.GtkWidget.__init__(self)

    # Init scintilla
    if _obj: self._o = _obj; return
    self._o = libscint.scintilla_new()

  #----------------------------------------------------------------
  # Overridden misc methods that don't work with generic pygtk impl's

  # Show widget on screen
  def show(self):
    libscint.scintilla_show(self._o)

  #----------------------------------------------------------------
  # Overridden signal management so we can get non-GTK data types
  # out to the Python callback

  # Parameters to the signal handler are as defined in GTK except
  # for the following cases:
  #
  # 1) 'notify' signal
  #
  # This signal has several subtypes.  The first parameter is always
  # the signal subtype.  Following parameters depend on value of subtype:
  #
  #   * SCN_STYLENEEDED      : int position where style is needed
  #   * SCN_CHARADDED        : char added to text
  #   * SCN_SAVEPOINTREACHED : None
  #   * SCN_SAVEPOINTLEFT    : None
  #   * SCN_MODIFYATTEMPTRO  : None
  #   * SCN_KEY              : key code (char), key modifiers
  #   * SCN_DOUBLECLICK      : None
  #   * SCN_UPDATEUI         : None
  #   * SCN_MODIFIED         : int position, type (bitwise AND of modif
  #                            masks SC_MOD_*),
  #                            text (string), int length, lines_added
  #                            (<0 = # deleted; 0 = false; >0 = # added), int line
  #                            affected, int fold level now, int fold
  #                            level before modification
  #   * SCN_MACRORECORD      : Opaque parameter to pass back to
  #                            macro_action().  May be serialized with
  #                            pickle.
  #   * SCN_MARGINCLICK      : key modifiers, int line start pos,
  #                            int margin identifier
  #   * SCN_NEEDSHOWN        : int char position, int length
  #
  # SCN_KEY differs from the generic GTK 'key_press_event' signal because
  # it is emitted only when the key doesn't result in an SCN_CHARADDED
  # signal.  It includes information about shift, alt, and control special
  # keys, and is emitted both when these keys are pressed and again if a
  # regular key is pressed afterwards while shift, control, or alt are
  # still held down.  E.g., if the user types alt-shift-C, it is emitted
  # once of alt, once for shift, and once for the whole combination.  The
  # key modifiers are only set for the latter and are defined as a bitwise
  # OR of the following:
  #
  #   SHIFT_PRESSED
  #   CTRL_PRESSED
  #   ALT_PRESSED
  #
  # So, e.g., 'modifiers & SHIFT_PRESSED' is true only when the shift key
  # was down during the press of a regular key.
  #
  # SCN_STYLENEEDED is part of the styling interface and indicates that
  # Scintilla needs style information starting at the given character
  # position in the text.  Scintilla then expects any styling updates to be
  # made via the styling call interface (defined below).
  #
  # 2) 'command' signal
  #
  # This is more of a 'changed' signal; scintilla currently includes no
  # information about the nature of the command or change.
  #

  class __cnv:
    def __init__(self, func):
      self.func = func
    def __call__(self, *args):
      a = list(args)
      for i in range(len(args)):
        if type(args[i]) == _gtk.GtkObjectType:
          a[i] = gtk._obj2inst(args[i])
        elif type(args[i]) == _gtk.GtkAccelGroupType:
          a[i] = gtk.GtkAccelGroup(_obj=args[i])
      a = tuple(a)
      ret = apply(self.func, a)
      if hasattr(ret, '_o'): ret = ret._o
      elif hasattr(ret, '_ag'): ret = ret._ag
      elif hasattr(ret, '_im'): ret = ret._im
      return ret

  def connect(self, name, f, *extra):
    if name == "command" or name == "notify":
      callback = self.__cnv(f)
      return libscint.scintilla_signal_connect(self._o, name,
                 callback.__call__, extra)
    else:
      return gtk.GtkWidget.connect(self, name, f, extra)
  signal_connect = connect

  def connect_after(self, name, f, *extra):
    if name == "command" or name == "notify":
      callback = self.__cnv(f)
      return libscint.scintilla_signal_connect_after(self._o, name,
                   callback.__call__, extra)
    else:
      return gtk.GtkWidget.connect_after(self, name, f, extra)
  signal_connect_after = connect_after

  def connect_object(self, name, f, obj, *extra):
    if name == "command" or name == "notify":
      callback = self.__cnv(f)
      return libscint.scintilla_signal_connect_object(self._o, name,
                    callback.__call__,
                    obj._o, extra)
    else:
      return gtk.GtkWidget.connect_object(self, name, f, extra)
  signal_connect_object = connect_object

  def connect_object_after(self, name, f, obj, *extra):
    if name == "command" or name == "notify":
      callback = self.__cnv(f)
      return libscint.scintilla_signal_connect_object_after(self._o, name,
                    callback.__call__,
                    obj._o, extra)
    else:
      return gtk.GtkWidget.connect_object_after(self, name, f, extra)
  signal_connect_object_after = connect_object_after

  #----------------------------------------------------------------
  # Text dimensions

  # Get total length of text in widget
  def get_textlength(self):
    return libscint.scintilla_get_textlength(self._o)
    
  # Get number of lines of text in widget (if empty, 1 is returned;
  # you can check if document is totally empty with get_textlength)
  def get_linecount(self):
    return libscint.scintilla_get_linecount(self._o)
    
  # Get length of given line (0=first) : This returns the number
  # of chars on the given line from start of line (w/o looking
  # at whether the end of line is due to '\r' or '\n' or a line wrap)
  def get_linelength(self, lineno):
    return libscint.scintilla_get_linelength(self._o, lineno, 1)

  # Get position (char offset from start of text) for the start of the given
  # line number (0=first).  If lineno < 0, uses the line at which the
  # current selection starts (or location of insertion cursor).
  def get_lineposition(self, lineno):
    return libscint.scintilla_get_lineposition(self._o, lineno)

  # Get the line (0=first) at given character position (0=first);
  # if pos < 0 then start of current selection is used for pos.
  def get_line_at_position(self, pos):
    return libscint.scintilla_get_lineatposition(self._o, pos)

  # Get position information for text at given screen mousepoint
  # Returns a tuple ( pos, line ) indicating character position from
  # start of text (0=first) and line number in text (0=first)
  def get_position_at_point(self, x, y):
    return libscint.scintilla_get_positionatpoint(self._o, x, y)

  # Get the screen point (x, y) at given character position; if pos < 0,
  # then (0,0) is returned.
  def get_point_at_position(self, pos):
    return libscint.scintilla_get_pointatposition(self._o, pos)

  #----------------------------------------------------------------
  # Text access

  # Get the text for given line (0=first) in widget
  def get_line(self, lineno):
    return libscint.scintilla_get_line(self._o, lineno)

  # Get range of text for given character range (inclusive; 0=first char).
  # If end == -1, returns text from start to end of document.
  def get_text_range(self, start, end):
    return libscint.scintilla_get_textrange(self._o, start, end)

  # Get all text in the widget
  def get_text(self):
    return libscint.scintilla_get_text(self._o)

  # Set all text in the widget replacing any existing text
  def set_text(self, str):
    libscint.scintilla_set_text(self._o, str)

  # Replace text in current selection with given text
  def replace_selection(self, str):
    libscint.scintilla_replace_selection(self._o, str)

  # Insert text in the widget at given character position (0=at start)
  # Omit position to insert at current insertion point
  def insert_text(self, str, pos=-1):
    libscint.scintilla_insert_text(self._o, pos, str)

  # Search for given text in the widget; optionally pass in the starting
  # and ending positions to search within (defaults to whole document
  # forward search from start), and flags indicating whether to match case
  # and match only whole words.  Returns (start, end) tuple of character
  # position of the match in the widget.  To match repeatedly, increment
  # start with each call, setting it to the end position of the previous
  # search.  You can also seach backward by passing values so that start >
  # end.
  def find_text(self, txt, start=0, end=-1, match_case=0, whole_words=0):
    if end == -1:
      end = self.get_textlength()
    return libscint.scintilla_find_text(self._o, start, end, txt, \
                                          match_case, whole_words) 

  # Set the search anchor for subsequent searches to the current
  # selection start position
  def search_anchor(self):
    libscint.scintilla_search_anchor(self._o)

  # Search to next text match from defined search anchor and
  # select the found text, if any (if none found, selection isn't changed)
  # Caller must call scroll_to_cursor() if desired; this is not automatic.
  def search_next(self, text, match_case, whole_words):
    return libscint.scintilla_search(self._o, text, match_case, whole_words, 1)

  # Search backward to previoud text match from defined search anchor and
  # select the found text, if any (if none found, selection isn't changed)
  # Caller must call scroll_to_cursor() if desired; this is not automatic.
  def search_prev(self, text, match_case, whole_words):
    return libscint.scintilla_search(self._o, text, match_case, whole_words, -1)

  #----------------------------------------------------------------
  # Clipboard support

  # Execute clipboard cut/copy/paste/clear/clearall
  def cut(self):
    libscint.scintilla_cut(self._o)
  def copy(self):
    libscint.scintilla_copy(self._o)
  def paste(self):
    libscint.scintilla_paste(self._o)
  def clear(self):
    libscint.scintilla_clear(self._o)
  def clearall(self):
    libscint.scintilla_clear_all(self._o)
    
  def can_paste(self):
    return libscint.scintilla_can_paste(self._o)

  #----------------------------------------------------------------
  # Text selection / positioning

  # Select all text in widget
  def select_all(self):
    libscint.scintilla_select_all(self._o)

  # Get currently selected range in the widget.  Returns (start, end)
  # character offsets (inclusive).
  def get_selection(self):
    return libscint.scintilla_get_selection(self._o)

  # Set selected range in widget.  If start < 0 then start is set to
  # end, so that the selection is set to an insertion point at current
  # end position of selection.  If end < 0 then end is set to end 
  # of document.  If both are < 0 then selection is set to insertion
  # point at very end of document.
  def set_selection(self, start, end):
    libscint.scintilla_set_selection(self._o, start, end)

  # Get first visible line in widget (0=first)
  def get_first_visible_line(self):
    return libscint.scintilla_get_firstvisibleline(self._o)

  # Scroll widget to given line (0=first)
  def scroll_to_line(self, lineno):
    libscint.scintilla_scrolltoline(self._o, lineno, 0)

  # Ensure caret is visible
  def move_caret_inside_view(self):
    libscint.scintilla_movecaretinsideview(self._o)

  # Scroll widget to given character position (char offset from start)
  def scroll_to_position(self, pos):
    libscint.scintilla_scrolltoposition(self._o, pos)

  # Scroll widget so the cursor (insertion point) is visible
  def scroll_to_cursor(self):
    libscint.scintilla_scrolltocursor(self._o)

  # Hide/show the current selection
  def hide_selection(self, hide_it=1):
    libscint.scintilla_hideselection(self._o, hide_it)

  # Get number of visible lines on screen
  def lines_on_screen(self):
    return libscint.scintilla_linesonscreen(self._o)

  #----------------------------------------------------------------
  # Undo/redo support

  # Check whether an 'undo' can be done
  def can_undo(self):
    return libscint.scintilla_can_undo(self._o)

  # Check whether a 'redo' can be done
  def can_redo(self):
    return libscint.scintilla_can_redo(self._o)

  # Undo latest change to widget
  def undo(self):
    libscint.scintilla_undo(self._o)

  # Redo latest undone change to widget
  def redo(self):
    libscint.scintilla_redo(self._o)

  # Clear out the undo buffer, discarding all undo record info
  def empty_undo_buffer(self):
    libscint.scintilla_emptyundobuffer(self._o)

  # Mark start of an undoable action
  def start_undo_action(self):
    libscint.scintilla_beginundoaction(self._o)

  # Mark end of an undoable action
  def end_undo_action(self):
    libscint.scintilla_endundoaction(self._o)

  #----------------------------------------------------------------
  # Indentation support

  def set_tabwidth(self, tabwidth):
    """ Set tab size for text display; values <= 0 are ignored """
    libscint.scintilla_set_tabwidth(self._o, tabwidth)

  def set_indent(self, indent):
    """ Set indent size for text display; values <= 0 are ignored """
    libscint.scintilla_set_indent(self._o, indent)

  def set_tabs_indent(self, usetabs=0):
    """ Set whether or not these indentation calls will insert tabs
    and spaces for indent, rather than spaces only) """

    libscint.scintilla_set_tabs_indent(self._o, usetabs)

  def set_line_indent(self, line, indent):
    """ Set the indentation level for given line.  Indentation
    is measured in spaces and will be made up of either spaces,
    tabs, or a mixture of spaces and tabs, depending on the
    configuration set previously with set_tabs_indent() and
    set_tabwidth() """

    libscint.scintilla_set_line_indent(self._o, line, indent)

  def get_line_indent(self, line):
    """ Get the given line's indent level, measured in spaces (tabs
    are counted according to set_tabwidth() value). """

    return libscint.scintilla_get_line_indent(self._o, line)

  def get_line_indent_pos(self, line):
    """ Get position of the first non-blank character in the given line """
    
    return libscint.scintilla_get_line_indent_pos(self._o, line)

  def get_pos_column(self, pos):
    """ Get column number in text for given document position """

    return libscint.scintilla_get_pos_column(self._o, pos)

  #----------------------------------------------------------------
  # Display mode control

  # Set widget to be readonly or not
  def set_readonly(self, ro=1):
    libscint.scintilla_set_readonly(self._o, ro)

  # Check whether widget has been modified by user
  def is_modified(self):
    return libscint.scintilla_ismodified(self._o)

  # Mark widget as having just been saved
  def set_savepoint(self):
    libscint.scintilla_set_savepoint(self._o)

  # Check whether widget is displaying whitespace indicators on text
  def showing_whitespace(self):
    return libscint.scintilla_get_viewwhitespace(self._o)

  # Show/hide white space indicators in the widget
  def show_whitespace(self, showit=1):
    libscint.scintilla_set_viewwhitespace(self._o, showit)

  # Check whether widget is displaying indent indicators on text
  def showing_indentguides(self):
    return libscint.scintilla_get_indent_guides(self._o)

  # Show/hide indent indicators
  def show_indentguides(self, showit=1):
    libscint.scintilla_set_indent_guides(self._o, showit)

  # Set left margin area width.  i is the margin number (0=line numbers,
  # 1=selection margin, 2= spacing margin) and w is the width (0=hide); 
  # values >= 100 are ignored
  def set_margin_widthn(self, i, w):
    libscint.scintilla_set_marginwidthn(self._o, i, w)

  # Set/disable use of buffered drawing
  def use_buffer(self, buf=1):
    libscint.scintilla_set_buffereddraw(self._o)

  # Set/disable use of palette realization in drawing
  def use_palette(self, pal=1):
    libscint.scintilla_set_usepalette(self._o, pal)

  # Set characters defining a word break
  def set_word_break_chars(self, chars):
    libscint.scintilla_set_word_break_chars(self._o, chars)
 
  # Set speed of cursor blinking in milliseconds (<=0 not to blink)
  def set_cursor_speed(self, speed):
    libscint.scintilla_set_cursor_speed(self._o, speed)

  # Set cursor display policy:  Either CURSOR_CENTER or CURSOR_SLOP
  # Used for style of scrolling to cursor when cursor is off-screen
  def set_cursor_policy(self, policy=CURSOR_SLOP, slop=0):
    libscint.scintilla_set_cursor_policy(self._o, policy, slop)

  def get_cursor(self):
    """ Get the current cursor shape """
    libscint.scintilla_get_cursor(self._o)

  def set_cursor(self, cursor):
    """ Set cursor shape, either SC_CURSORNORMAL or SC_CURSORWAIT """
    libscint.scintilla_set_cursor(self._o, cursor)

  def set_eol_mode(self, mode):
    """ Set end-of-line mode: controls what is inserted during typing
    for an end-of line.  Use one of EOL_CRLF, EOL_CR, or EOL_LF """

    libscint.scintilla_set_eol_mode(self._o, mode)

  def get_eol_mode(self, mode):
    """ Get end-of-line mode.  Will be one of EOL_CRLF, EOL_CR, or EOL_LF """

    return libscint.scintilla_get_eol_mode(self._o)

  # Toggle type-over/insert edit mode
  def toggle_overtype(self):
    libscint.scintilla_toggle_overtype(self._o)

  # Zoom text display in to high level of magnification
  def zoomin(self):
    libscint.scintilla_zoom_in(self._o)

  # Zoom text display out to lower level of magnification
  def zoomout(self):
    libscint.scintilla_zoom_out(self._o)

  # Show/hide end-of-line characters (0=hide)
  def show_eol(self, show=1):
    libscint.scintilla_show_eol(self._o, show)

  # Get visibility of end-of-line characters (0=hidden)
  def showing_eol(self):
    return libscint.scintilla_showing_eol(self._o)

  # Set action for given command key.  Modifier is one of
  # SHIFT_PRESSED, CTRL_PRESSED, or ALT_PRESSED.  Key is
  # either a character or one of the values defined in 
  # pygtk's GDK.py.  Action is one of the defined scintilla actions
  # from one of the groups defined at the top of this file.
  def set_cmdkey(self, key, modifier, action):
    libscint.scintilla_set_cmdkey(self._o, key, modifier, action)

  # Clear action for given command key
  def clear_cmdkey(self, key, modifier):
    libscint.scintilla_clear_cmdkey(self._o, key, modifier)

  # Clear all assigned command keys
  def clear_allcmdkeys(self):
    libscint.scintilla_clear_allcmdkeys(self._o)

  # Enabled/disable the default popup menu
  def enable_defaultpopup(self, enable=1):
    libscint.scintilla_enable_defaultpopup(self._o, enable)

  # Show/hide the horizontal scrollbar on the editor
  def show_hscrollbar(self, visible=1):
    libscint.scintilla_show_hscrollbar(self._o, visible)

  #----------------------------------------------------------------
  # Top-level style/color defaults

  # Set default display style for available builtin editor areas.  One of:
  #
  #   STYLE_DEFAULT      -- The default text style
  #   STYLE_LINENUMBER   -- Style for the line numbering area
  #   STYLE_BRACELIGHT   -- Style for brace hilighting 
  #   STYLE_BRACEBAD     -- Style for bad brace hilighting
  #   STYLE_CONTROLCHAR  -- Style for control characters in the text
  #
  # The font is a font name as a string (can be any font installed on the
  # host system).  Size is size in points.  Set bold, italic = 1 to use bold
  # and/or italic.  Colors are (r,g,b) (where (0,0,0) is black and
  # (0xff,0xff,0xff) is white.
  def set_builtin_style(self, area, font, size, bold, italic, \
                        forecolor, backcolor, eolfilled=0, \
                        underline=0, charset=SC_CHARSET_DEFAULT):
    libscint.scintilla_set_builtin_style(self._o, area, font, size, \
      bold, italic, forecolor, backcolor, eolfilled, underline, charset)

  # Set selection forecolor display color to desired value.  Pass set=0 to 
  # return to default color.
  def set_selection_fore_color(self, set, color):
    libscint.scintilla_set_selection_fore_color(self._o, set, color)

  # Set selection background display color to desired value.  Pass set=0 to 
  # return to default color.
  def set_selection_back_color(self, set, color):
    libscint.scintilla_set_selection_back_color(self._o, set, color)

  # Set cursor's color to desired value.
  def set_cursor_color(self, color):
    libscint.scintilla_set_cursor_color(self._o, color)


  #----------------------------------------------------------------
  # Styled text access

  # Style information is embedded in the text string so that each
  # character is followed by a byte of style information, so text 
  # passed to these functions will be 2X the length indicated by any
  # length parameter.  The style byte is set to a style code defined
  # with the define_text_style().  A style byte of 0 indicates no
  # styling (except the default style set with set_builtin_style().

  # Define a text style.  The stype arg must be from 1 to STYLE_MAX.
  # eol_filled indicates whether or not to apply the style to the
  # end-of-line character(s). See set_builtin_style() above for 
  # info on the other parameters.
  def define_text_style(self, stype, font, size, bold, italic, \
                        forecolor, backcolor, eol_filled, underline=0, \
                        charset=SC_CHARSET_DEFAULT):
    libscint.scintilla_define_text_style(self._o, stype, font, size, \
      bold, italic, forecolor, backcolor, eol_filled, underline, charset)

  def set_style_font(self, stype, font_name):
    """ Set the font used for the given style.  Use STYLE_DEFAULT for the stype
    to set the default font. """
    libscint.scintilla_set_style_font(self._o, stype, font_name)

  def set_style_size(self, stype, size):
    """ Set the size of the font used for the given style.  Use
    STYLE_DEFAULT for stype to set the size of the default font. """

    libscint.scintilla_set_style_size(self._o, stype, size)

  def set_style_bold(self, stype, bold):
    """ Set the bold attribute of the font used for the given style.  Use
    STYLE_DEFAULT for stype to set the bold attribute of the default
    font. """
    libscint.scintilla_set_style_bold(self._o, stype, bold)

  def set_style_italic(self, stype, italic):
    """ Set the italic attribute of the font used for the given style.  Use
    STYLE_DEFAULT for stype to set the italic attribute of the default
    font. """
    libscint.scintilla_set_style_italic(self._o, stype, italic)

  def set_style_eolfilled(self, stype, eolfilled):
    """ Set the eolfilled attribute of the font used for the given style.  Use
    STYLE_DEFAULT for stype to set the eolfilled attribute of the default
    font. """
    libscint.scintilla_set_style_eolfilled(self._o, stype, eolfilled)

  def __hex_to_int(self, hex_string):
    """ Converts string of hex digits into an integer. """
    val = 0
    for i in range(0, len(hex_string)):
      digit = string.upper(hex_string[i])
      if digit not in string.hexdigits:
        raise ValueError
      if digit == 'A': d_val = 10
      elif digit == 'B': d_val = 11
      elif digit == 'C': d_val = 12
      elif digit == 'D': d_val = 13
      elif digit == 'E': d_val = 14
      elif digit == 'F': d_val = 15
      else: d_val = string.atoi(digit)

      exp = (len(hex_string) - i) - 1
      val = val + d_val * (16 ** exp)

    return val
  
  def __xform_color(self, col):
    """ Transforms value for color into integer that scintilla expects. """

    if type(col) == type(1):
      return col
    elif type(col) == type(()) and len(col) == 3:
      r, g, b = col
      val = ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff);
      return val
    elif type(col) == type('') and len(col) != 0:
      if col[0] == '#' and len(col) == 7:
        rgb = (self.__hex_to_int(col[1:3]), self.__hex_to_int(col[3:5]),
               self.__hex_to_int(col[5:7]))
        return self.__xform_color(rgb)

    raise ValueError, 'Incorrect color value: ' + repr(col)
    
  def set_style_fore(self, stype, fore):
    """ Set the foreground color of the font used for the given style.  Use
    STYLE_DEFAULT for stype to set the foreground color of the default
    font. """

    libscint.scintilla_set_style_fore(self._o, stype, self.__xform_color(fore))
    
  def set_style_back(self, stype, back):
    """ Set the background color of the font used for the given style.  Use
    STYLE_DEFAULT for stype to set the background color of the default
    font. """

    libscint.scintilla_set_style_back(self._o, stype, self.__xform_color(back))

  # Clear all style definitions
  def clear_all_styles(self):
     libscint.scintilla_clear_all_styles(self._o)

  # Get styled text for inclusive given range of text (0=first char)
  def get_styled_text(self, start, end):
    return libscint.scintilla_get_styled_text(self._o, start, end)

  # Get style byte for text at given character position
  def get_style_at(self, pos):
    return libscint.scintilla_get_style_at(self._o, pos)

  # Insert styled text at current location, up to the given number of
  # characters.
  def insert_styled_text(self, text, len):
    libscint.scintilla_insert_style_text(self._o, text, len)

  #----------------------------------------------------------------
  # Lexer-based styling support

  # This is used with scintilla's built-in lexers to control colorizing
  # that happens automatically in a document.
  #
  # An example of use is as follows:
  # 
  # 1) Obtain mime type of your text
  # 2) Convert mime type into sci_names listed document type
  #    (e.g., kCppDocument)
  # 3) Determine the lexer to use for the document type with
  #    lexer_id = scintilla.kLexerForDocType[doctype]
  # 4) Set the lexer with scint.set_lexer(lexer_id, doctype)

  # This uses the default configured styles defined in def_lexer_info.py.
  # (which is generated from the SciTE properties file by _gen_def_lex_info.py 
  # but which can also be hand-modified to arrive at different defaults).
  # These can be accessed with get_default_styles() or you can also set 
  # other styles with set_styles()

  def set_lexer(self, lexer, doc_type = None, use_defaults = gtk.TRUE):
    """ Enable lexing using builtin lexer. """
    
    libscint.scintilla_set_lexer(self._o, lexer)
    if use_defaults:
      keywords = def_lexer_info.kDefaultKeywords.get(doc_type)
      if keywords != None:
        self.set_keywords(0, keywords)
      styles = self.get_default_styles(lexer)
      self.set_styles(styles)

  def get_lexer(self):
    """ Return the builtin lexer currently enabled. """
    
    return libscint.scintilla_get_lexer(self._o)

  def get_default_styles(self, lexer_id):
    """ Get the default styles for use with given lexer. """

    return def_lexer_info.kDefStylesForLexer.get(lexer_id)

  def set_styles(self, styles):
    """ Set styles for syntax styling according to styles dictionary. """

    # Clear all styles to defaults if no info given
    if styles == None:
      for id in range(0, STYLE_FIRSTRESERVED):
        self.set_style_font(id, 'lucidatypewriter')
        self.set_style_size(id, 12)
        self.set_style_fore(id, "#000000")
        self.set_style_back(id, "#FFFFFF")
        self.set_style_bold(id, 0)
        self.set_style_italic(id, 0)
        self.set_style_eolfilled(id, 0)      
      for id in range(STYLE_LASTRESERVED+1, STYLE_MAX+1):
        self.set_style_font(id, 'lucidatypewriter')
        self.set_style_size(id, 12)
        self.set_style_fore(id, "#000000")
        self.set_style_back(id, "#FFFFFF")
        self.set_style_bold(id, 0)
        self.set_style_italic(id, 0)
        self.set_style_eolfilled(id, 0)      
      return

    # Set style info for each given style id
    for id, defn in styles.items():

      # Set the styles for this id
      items_set = []
      for name, value in defn.items():
        if name == 'font':
          self.set_style_font(id, value)
        elif name == 'size':
          self.set_style_size(id, value)
        elif name == 'fore':
          self.set_style_fore(id, value)
        elif name == 'back':
          self.set_style_back(id, value)
        elif name == 'bold':
          self.set_style_bold(id, value)
        elif name == 'italic':
          self.set_style_italic(id, value)
        elif name == 'eol_filled':
          self.set_style_eolfilled(id, value)
        items_set.append(name)
       
      # Set all values not explicitely set to the defaults
      if not 'font' in items_set:
        self.set_style_font(id, 'lucidatypewriter')
      if not 'size' in items_set:
        self.set_style_size(id, 12)
      if not 'fore' in items_set:
        self.set_style_fore(id, "#000000")
      if not 'back' in items_set:
        self.set_style_back(id, "#FFFFFF")
      if not 'bold' in items_set:
        self.set_style_bold(id, 0)
      if not 'italic' in items_set:
        self.set_style_italic(id, 0)
      if not 'eol_filled' in items_set:
        self.set_style_eolfilled(id, 0)

  def set_property(self, name, value):
    """ Set named property to value for this editor only. """

    libscint.scintilla_set_property(self._o, name, str(value))

  def set_keywords(self, keywordset, keywords):
    """ Set the keywords to be used in the keyword set to be used by the
    builtin lexer.  The keywords argument may either be a string of words
    seperated by a space or something that can be passed to sting.join
    to create such a string. """

    if type(keywords) != type(''):
      keywords = string.join(keywords, ' ')
    libscint.scintilla_set_keywords(self._o, keywordset, keywords)

  def colourise(self, start, end):
    """ Colourise the given range of text. """
    
    libscint.scintilla_colourise(self._o, start, end)

  #----------------------------------------------------------------
  # Interactive styling support

  # This is used in conjunction with the SCN_STYLENEEDED sub-type of 
  # the 'notify' signal in order to apply styling information to plain
  # text according to external analysis of the text (e.g., for syntax
  # hilighting)

  # The styles set are defined with define_text_style() above. See
  # previous section for more info.

  # Get position of last character that has been styled interactively so far
  def styling_get_end(self):
    return libscint.scintilla_styling_get_end(self._o)

  # Start styling text at the given position.  Subsequent calls to
  # styling_apply() will apply the given mask with bitwise AND to 
  # all existing style information so that only the bits that match 
  # the mask are retained and the ORD-ed with the new style that is
  # applied.  Pass a mask of 0 to replace all existing style information.
  def styling_start(self, pos, mask):
    libscint.scintilla_styling_start(self._o, pos, mask)

  # Apply given style to position set with most recent styling_start()
  # call.  The style parameter is the style id to apply (one of
  # the available style ids, which may have been set up previously
  # with define_text_style().  The given style is applied to next 
  # len characters and the styling end position returned by 
  # get_styling_end() is updated to point to the last character that 
  # has been styled.
  def styling_apply(self, len, style):
    libscint.scintilla_styling_apply(self._o, len, style)

  #----------------------------------------------------------------
  # Indicator support
 
  # This allows setting of additional styling information on top of
  # syntax hilighting or other styling (e.g., to indicate bad parts
  # of code).  The indicator styling set is not affected by subsequent
  # normal styling.

  # There are currently only three indicator styles, which
  # are set with set_indic_style() with indicator numbers 0, 1, and 2
  # and the indicator styles INDIC_PLAIN, INDIC_SQUIGGLE, INDIC_TT, 
  # INDIC_DIAGONAL, and INDIC_STRIKE.  Subsequently, you can set indicators on 
  # ranges of text by calling styling_start() with mask=INDICS_MASK
  # and then doing an interactive styling session, responding to
  # the SCN_STYLENEEDED variant of the 'notify' signal with calls
  # to styling_apply() with one or more of the three indicator masks
  # INDIC0_MASK, INDIC1_MASK, or INDIC2_MASK

  def set_indic_style(self, mask, style):
    libscint.scintilla_set_indic_style(self._o, mask, style)

  def get_indic_style(self, mask):
    return libscint.scintilla_get_indic_style(self._o, mask)

  def set_indic_forecolor(self, mask, color):
    libscint.scintilla_set_indic_forecolor(self._o, mask, color)

  def get_indic_forecolor(self, mask):
    return libscint.scintilla_get_indic_forecolor(self._o, mask)

  #----------------------------------------------------------------
  # Autocompletion

  def autocomplete_start(self, len_completed, word_list):
    """ Start autocompletion at the current cursor position.  len_completed is
    the number of characters before the current cursor position that are
    considered part of the word to complete.  word_list may either be a
    list of strings or a single string containing words seperated by
    spaces. """
    
    if type(word_list) != type(''):
      word_list = string.join(word_list, ' ')
    libscint.scintilla_autocomplete_start(self._o, len_completed, word_list)
    
  def autocomplete_cancel(self):
    """ Cancel autocompletion. """
    
    libscint.scintilla_autocomplete_cancel(self._o)

  def autocomplete_completed(self):
    """ Complete the current autocompletion. """
    
    libscint.scintilla_autocomplete_completed(self._o)

  def set_autocomplete_stopchars(self, char_list):
    """ Sets autocompletion stop characters.  The char_list can be a string
    or a list of strings. """
    
    if type(char_list) != type(''):
      char_list = string.join(char_list, ' ')
    libscint.scintilla_set_autocomplete_stopchars(self._o, char_list)

  def is_autocomplete_active(self):
    """ Returns whether autocompletion is active. """
    
    return libscint.scintilla_is_autocomplete_active(self._o)

  def get_autocomplete_startpos(self):
    """ Get the position of the beginning of the active autocompletion. """
    
    return libscint.scintilla_get_autocomplete_startpos(self._o)

  def get_autocomplete_cancel_at_startpos(self):
    """ Get the value of the flag indicating whether autocompletion should be canceled
    if the cursor is positioned before the start position. """
    
    return libscint.scintilla_get_autocomplete_cancel_at_startpos(self._o)

  def set_autocomplete_cancel_at_startpos(self, val):
    """ Set the value of the flag indicating whether autocompletion should be canceled
    if the cursor is positioned before the start position. """
    
    return libscint.scintilla_set_autocomplete_cancel_at_startpos(self._o, val)

  #----------------------------------------------------------------
  # Call tips

  def show_calltip(self, pos, text):
    """ Show calltip window starting at given text position, filled
    with given calltip text """

    libscint.scintilla_show_calltip(self._o, pos, text)

  def hide_calltip(self):
    """ Hide the calltips window """

    libscint.scintilla_hide_calltip(self._o)

  def is_calltip_active(self):
    """ Check whether calltip window is currently active """

    return libscint.scintilla_is_calltip_active(self._o)

  def get_calltip_startpos(self):
    """ Get starting position in text where calltip window was shown """

    return libscint.scintilla_get_calltip_startpos(self._o)

  def set_calltip_hilite(self, start, end):
    """ Set portion of calltip text to hilight """

    libscint.scintilla_set_calltip_hilite(self._o, start, end)

  def set_calltip_backcolor(self, color):

    libscint.scintilla_set_calltip_backcolor(self._o, color)

  #----------------------------------------------------------------
  # Brace highlighting

  def brace_match(self, pos, max_restyle = 0):
    """ Finds a corresponding matching brace given the position of one
    brace. The brace characters handled are '(', ')', '[', ']', '{',
    '}', '<', and '>'. A match only occurs if the style of the
    matching brace is the same as the starting brace or the matching
    brace is beyond the end of styling. Nested braces are handled
    correctly. The max_restyle parameter must currently be 0.Return the
    ma. """
    
    return libscint.scintilla_brace_match(self._o, pos, max_restyle)
    
  def brace_highlight(self, pos1, pos2):
    """ Highlight characters at positions given in the 'brace highlighting
    style', which is defined as style number 34.  Clears any previous
    brace highlight or brace badlight.  Call with position of -1 to
    clear all brace lighting. """
    
    libscint.scintilla_brace_highlight(self._o, pos1, pos2)

  def brace_badlight(self, pos):
    """ Highlight character at position given in the 'brace badlighting
    style', which is defined as style number 35.  Clears any previous
    brace highlight or brace badlight.  Call with position of -1 to
    clear all brace lighting. """
    
    libscint.scintilla_brace_badlight(self._o, pos)
    
  #----------------------------------------------------------------
  # Marker support : Allows insertion of marks in the left margin
  # of the text editor, e.g. to mark breakpoints in a debugger

  # Define a new type of marker for text:  The mtype must be between
  # 1 and MARKER_MAX and the mstyle one of those defined at the top
  # of this file.  Any existing marker of given type is changed to
  # the new given values.  The forecolor/backcolor are tuples of
  # one-byte RGB values so that (0,0,0) is black and (0xff,0xff,0xff)
  # is white.
  def define_marker_type(self, mtype, mstyle=MARK_CIRCLE, \
                         forecolor = (0,0,0), \
                         backcolor = (0xff,0xff,0xff)):
    libscint.scintilla_define_marker_type(self._o, mtype, mstyle, forecolor, \
                                  backcolor)

    
  # Place a marker at given line (0=first), using a previously 
  # defined marker type.  Returns a handle to the marker that will
  # remain unchanged even if the line number for the marker changes
  # as a result of edits
  def place_marker_at_line(self, lineno, mtype):
    return libscint.scintilla_place_marker_at_line(self._o, lineno, mtype)

  # Get line number for given marker (0=first line, -1 if invalid/deleted)
  def get_marker_line(self, mhandle):
    return libscint.scintilla_get_marker_line(self._o, mhandle)

  # Remove marker using its handle
  def remove_marker(self, mhandle):
    libscint.scintilla_remove_marker(self._o, mhandle)

  # Remove marker of given pre-defined type from given line (0=first)
  def remove_marker_at_line(self, lineno, mtype):
    libscint.scintilla_remove_marker_at_line(self._o, lineno, mtype)

  # Remove all markers of given type from the editor
  def remove_all_markers(self, mtype):
    libscint.scintilla_remove_markers_by_type(self._o, mtype)

  # Get marker at given line (0=first).  Returns the defined marker 
  # type, 0 if there is no marker
  def get_marker(self, lineno):
    return libscint.scintilla_get_marker_at_line(self._o, lineno)

  # Find the next marker starting at given line (0=first), inclusive.  
  # Returns the line number where the next marker is found.
  def next_marker(self, lineno):
    return libscint.scintilla_next_marker(self._o, lineno)

  # Find the previous marker starting at given line (0=first), inclusive,
  # looking backwards in the file.
  # Returns the line number where the previous marker is found.
  def prev_marker(self, lineno):
    return libscint.scintilla_prev_marker(self._o, lineno)


  #----------------------------------------------------------------
  # Macro support : Requires MACRO_SUPPORT to be turned on at
  # compile time in Scintilla.h

  # Start recording macros.  SCN_MACRORECORD signals will be sent
  # subsequently to the callback connected to 'notify' signal with 
  # connect() until stop_record() is called.
  def start_record(self):
    libscint.scintilla_start_record(self._o)

  # Stop macro recording
  def stop_record(self):
    libscint.scintilla_stop_record(self._o)

  # Perform one action reported previously by SCN_MACRORECORD signal.
  # Call this repeatedly to execute a macro.  Each call returns a
  # failure status of non-0 when the action failed and the macro
  # should be aborted.
  def macro_action(self, args):
    return libscint.scintilla_macro_action(self._o, args)


  #----------------------------------------------------------------
  # Scripting / external key binding support

  # Move cursor in one of the defined ways.  Available options are:
  #
  # SCI_LINEDOWN, SCI_LINEDOWNEXTEND, SCI_LINEUP, SCI_LINEUPEXTEND,
  # SCI_CHARLEFT, SCI_CHARLEFTEXTEND, SCI_CHARRIGHT, SCI_CHARRIGHTEXTEND,
  # SCI_WORDLEFT, SCI_WORDLEFTEXTEND, SCI_WORDRIGHT, SCI_WORDRIGHTEXTEND,
  # SCI_HOME, SCI_HOMEEXTEND, SCI_LINEEND, SCI_LINEENDEXTEND,
  # SCI_DOCUMENTSTART, SCI_DOCUMENTSTARTEXTEND, SCI_DOCUMENTEND,
  # SCI_DOCUMENTENDEXTEND, SCI_PAGEUP, SCI_PAGEUPEXTEND, SCI_PAGEDOWN,
  # SCI_PAGEDOWNEXTEND, SCI_VCHOME, or SCI_VCHOMEEXTEND
  #
  # The 'EXTEND' movement calls extend the current selection as the
  # cursor is moved.
  def move_cursor(self, cmtype):
    libscint.scintilla_cursor_movement(self._o, cmtype)

  # Delete operations : Pass one of SCI_DELWORDLEFT, SCI_DELWORDRIGHT,
  # or SCI_DELETEBACK
  def delete_op(self, dtype):
    libscint.scintilla_delete_op(self._o, dtype)

  # Special key insertion : Pass one of SCI_TAB, SCI_BACKTAB,
  # SCI_FORMFEED, SCI_NEWLINE, or SCI_CANCEL
  def key_op(self, ktype):
    libscint.scintilla_key_op(self._o, ktype)


  #----------------------------------------------------------------
  # Document object support : Allows shared document object for
  # multiple editors or sharing with external views and editors
  # of various types

  def set_document(self, doc):
    """ Set the document used by editor.  If doc is None, the editor switches
    to a new, blank document. """
    libscint.scintilla_set_document(self._o, doc)

  def get_document(self, doc):
    """ Get the document used by editor. """
    libscint.scintilla_get_document(self._o)

create_document = libscint.create_document
create_lexer = libscint.create_lexer


#----------------------------------------------------------------
# Utilities
#----------------------------------------------------------------

def _GtkEventToScintillaKey(event):
  """ Convert Gtk event information into the form used by scintilla 
  for key event notification.  Returns ( keyval, modifier ). This
  matches how it's done in the C++ code. """

  if event.state & GDK.SHIFT_MASK and event.keyval < 128:
    keyval = ord(string.upper(chr(event.keyval)))
  elif event.keyval == GDK.ISO_Left_Tab:
    keyval = GDK.Tab
  else:
    keyval = event.keyval
  modifier = 0
  if event.state & GDK.SHIFT_MASK:
    modifier = modifier | SHIFT_PRESSED
  if event.state & GDK.CONTROL_MASK:
    modifier = modifier | CTRL_PRESSED
  if event.state & GDK.MOD1_MASK:
    modifier = modifier | ALT_PRESSED

  return keyval, modifier

