////////////////////////////////////////////////////////////////////////////
// Name:        stc.cpp
// Purpose:     A wxWindows implementation of Scintilla.  This class is the
//              one meant to be used directly by wx applications.  It does not
//              derive directly from the Scintilla classes, but instead
//              delegates most things to the real Scintilla class.
//              This allows the use of Scintilla without polluting the
//              namespace with all the classes and identifiers from Scintilla.
//
// Author:      Robin Dunn
//
// Created:     13-Jan-2000
// RCS-ID:      $Id: stc.cpp.in,v 1.28.2.7 2003/05/29 23:49:16 RD Exp $
// Copyright:   (c) 2000 by Total Control Software
// Licence:     wxWindows license
/////////////////////////////////////////////////////////////////////////////

#include <ctype.h>

#include "wx/stc/stc.h"
#include "ScintillaWX.h"

#include <wx/wx.h>
#include <wx/tokenzr.h>
#include <wx/mstream.h>
#include <wx/image.h>
#include <wx/file.h>


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

const wxChar* wxSTCNameStr = wxT("stcwindow");

#ifdef MAKELONG
#undef MAKELONG
#endif

#define MAKELONG(a, b) ((a) | ((b) << 16))


static long wxColourAsLong(const wxColour& co) {
    return (((long)co.Blue()  << 16) |
            ((long)co.Green() <<  8) |
            ((long)co.Red()));
}

static wxColour wxColourFromLong(long c) {
    wxColour clr;
    clr.Set(c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff);
    return clr;
}


static wxColour wxColourFromSpec(const wxString& spec) {
    // spec should be "#RRGGBB"
    long red, green, blue;
    red = green = blue = 0;
    spec.Mid(1,2).ToLong(&red,   16);
    spec.Mid(3,2).ToLong(&green, 16);
    spec.Mid(5,2).ToLong(&blue,  16);
    return wxColour(red, green, blue);
}

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

DEFINE_EVENT_TYPE( wxEVT_STC_CHANGE )
DEFINE_EVENT_TYPE( wxEVT_STC_STYLENEEDED )
DEFINE_EVENT_TYPE( wxEVT_STC_CHARADDED )
DEFINE_EVENT_TYPE( wxEVT_STC_SAVEPOINTREACHED )
DEFINE_EVENT_TYPE( wxEVT_STC_SAVEPOINTLEFT )
DEFINE_EVENT_TYPE( wxEVT_STC_ROMODIFYATTEMPT )
DEFINE_EVENT_TYPE( wxEVT_STC_KEY )
DEFINE_EVENT_TYPE( wxEVT_STC_DOUBLECLICK )
DEFINE_EVENT_TYPE( wxEVT_STC_UPDATEUI )
DEFINE_EVENT_TYPE( wxEVT_STC_MODIFIED )
DEFINE_EVENT_TYPE( wxEVT_STC_MACRORECORD )
DEFINE_EVENT_TYPE( wxEVT_STC_MARGINCLICK )
DEFINE_EVENT_TYPE( wxEVT_STC_NEEDSHOWN )
DEFINE_EVENT_TYPE( wxEVT_STC_POSCHANGED )
DEFINE_EVENT_TYPE( wxEVT_STC_PAINTED )
DEFINE_EVENT_TYPE( wxEVT_STC_USERLISTSELECTION )
DEFINE_EVENT_TYPE( wxEVT_STC_URIDROPPED )
DEFINE_EVENT_TYPE( wxEVT_STC_DWELLSTART )
DEFINE_EVENT_TYPE( wxEVT_STC_DWELLEND )
DEFINE_EVENT_TYPE( wxEVT_STC_START_DRAG )
DEFINE_EVENT_TYPE( wxEVT_STC_DRAG_OVER )
DEFINE_EVENT_TYPE( wxEVT_STC_DO_DROP )
DEFINE_EVENT_TYPE( wxEVT_STC_ZOOM )
DEFINE_EVENT_TYPE( wxEVT_STC_HOTSPOT_CLICK )
DEFINE_EVENT_TYPE( wxEVT_STC_HOTSPOT_DCLICK )
DEFINE_EVENT_TYPE( wxEVT_STC_CALLTIP_CLICK )



BEGIN_EVENT_TABLE(wxStyledTextCtrl, wxControl)
    EVT_PAINT                   (wxStyledTextCtrl::OnPaint)
    EVT_SCROLLWIN               (wxStyledTextCtrl::OnScrollWin)
    EVT_SCROLL                  (wxStyledTextCtrl::OnScroll)
    EVT_SIZE                    (wxStyledTextCtrl::OnSize)
    EVT_LEFT_DOWN               (wxStyledTextCtrl::OnMouseLeftDown)
    // Let Scintilla see the double click as a second click
    EVT_LEFT_DCLICK             (wxStyledTextCtrl::OnMouseLeftDown)
    EVT_MOTION                  (wxStyledTextCtrl::OnMouseMove)
    EVT_LEFT_UP                 (wxStyledTextCtrl::OnMouseLeftUp)
#if defined(__WXGTK__) || defined(__WXMAC__)
    EVT_RIGHT_UP                (wxStyledTextCtrl::OnMouseRightUp)
#else
    EVT_CONTEXT_MENU            (wxStyledTextCtrl::OnContextMenu)
#endif
    EVT_MOUSEWHEEL              (wxStyledTextCtrl::OnMouseWheel)
    EVT_MIDDLE_UP               (wxStyledTextCtrl::OnMouseMiddleUp)
    EVT_CHAR                    (wxStyledTextCtrl::OnChar)
    EVT_KEY_DOWN                (wxStyledTextCtrl::OnKeyDown)
    EVT_KILL_FOCUS              (wxStyledTextCtrl::OnLoseFocus)
    EVT_SET_FOCUS               (wxStyledTextCtrl::OnGainFocus)
    EVT_SYS_COLOUR_CHANGED      (wxStyledTextCtrl::OnSysColourChanged)
    EVT_ERASE_BACKGROUND        (wxStyledTextCtrl::OnEraseBackground)
    EVT_MENU_RANGE              (10, 16, wxStyledTextCtrl::OnMenu)
    EVT_LISTBOX_DCLICK          (-1, wxStyledTextCtrl::OnListBox)
END_EVENT_TABLE()


IMPLEMENT_CLASS(wxStyledTextCtrl, wxControl)
IMPLEMENT_DYNAMIC_CLASS(wxStyledTextEvent, wxCommandEvent)

#ifdef LINK_LEXERS
// forces the linking of the lexer modules
int Scintilla_LinkLexers();
#endif

//----------------------------------------------------------------------
// Constructor and Destructor

wxStyledTextCtrl::wxStyledTextCtrl(wxWindow *parent,
                                   wxWindowID id,
                                   const wxPoint& pos,
                                   const wxSize& size,
                                   long style,
                                   const wxString& name) :
    wxControl(parent, id, pos, size,
              style | wxVSCROLL | wxHSCROLL | wxWANTS_CHARS | wxCLIP_CHILDREN,
              wxDefaultValidator, name)
{
#ifdef LINK_LEXERS
    Scintilla_LinkLexers();
#endif
    m_swx = new ScintillaWX(this);
    m_stopWatch.Start();
    m_lastKeyDownConsumed = FALSE;
    m_vScrollBar = NULL;
    m_hScrollBar = NULL;
#if wxUSE_UNICODE
    // Put Scintilla into unicode (UTF-8) mode
    SetCodePage(wxSTC_CP_UTF8);
#endif
}


wxStyledTextCtrl::~wxStyledTextCtrl() {
    delete m_swx;
}


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

long wxStyledTextCtrl::SendMsg(int msg, long wp, long lp) {

    return m_swx->WndProc(msg, wp, lp);
}



//----------------------------------------------------------------------
// BEGIN generated section.  The following code is automatically generated
//       by gen_iface.py from the contents of Scintilla.iface.  Do not edit
//       this file.  Edit stc.cpp.in or gen_iface.py instead and regenerate.

%(METHOD_IMPS)s

// END of generated section
//----------------------------------------------------------------------


// Returns the line number of the line with the caret.
int wxStyledTextCtrl::GetCurrentLine() {
    int line = LineFromPosition(GetCurrentPos());
    return line;
}


// Extract style settings from a spec-string which is composed of one or
// more of the following comma separated elements:
//
//      bold                    turns on bold
//      italic                  turns on italics
//      fore:#RRGGBB            sets the foreground colour
//      back:#RRGGBB            sets the background colour
//      face:[facename]         sets the font face name to use
//      size:[num]              sets the font size in points
//      eol                     turns on eol filling
//      underline               turns on underlining
//
void wxStyledTextCtrl::StyleSetSpec(int styleNum, const wxString& spec) {

    wxStringTokenizer tkz(spec, wxT(","));
    while (tkz.HasMoreTokens()) {
        wxString token = tkz.GetNextToken();

        wxString option = token.BeforeFirst(':');
        wxString val = token.AfterFirst(':');

        if (option == wxT("bold"))
            StyleSetBold(styleNum, true);

        else if (option == wxT("italic"))
            StyleSetItalic(styleNum, true);

        else if (option == wxT("underline"))
            StyleSetUnderline(styleNum, true);

        else if (option == wxT("eol"))
            StyleSetEOLFilled(styleNum, true);

        else if (option == wxT("size")) {
            long points;
            if (val.ToLong(&points))
                StyleSetSize(styleNum, points);
        }

        else if (option == wxT("face"))
            StyleSetFaceName(styleNum, val);

        else if (option == wxT("fore"))
            StyleSetForeground(styleNum, wxColourFromSpec(val));

        else if (option == wxT("back"))
            StyleSetBackground(styleNum, wxColourFromSpec(val));
    }
}


// Set style size, face, bold, italic, and underline attributes from
// a wxFont's attributes.
void wxStyledTextCtrl::StyleSetFont(int styleNum, wxFont& font) {
    int      size     = font.GetPointSize();
    wxString faceName = font.GetFaceName();
    bool     bold     = font.GetWeight() == wxBOLD;
    bool     italic   = font.GetStyle() != wxNORMAL;
    bool     under    = font.GetUnderlined();

    // TODO: add encoding/charset mapping
    StyleSetFontAttr(styleNum, size, faceName, bold, italic, under);
}

// Set all font style attributes at once.
void wxStyledTextCtrl::StyleSetFontAttr(int styleNum, int size,
                                        const wxString& faceName,
                                        bool bold, bool italic,
                                        bool underline) {
    StyleSetSize(styleNum, size);
    StyleSetFaceName(styleNum, faceName);
    StyleSetBold(styleNum, bold);
    StyleSetItalic(styleNum, italic);
    StyleSetUnderline(styleNum, underline);

    // TODO: add encoding/charset mapping
}


// Perform one of the operations defined by the wxSTC_CMD_* constants.
void wxStyledTextCtrl::CmdKeyExecute(int cmd) {
    SendMsg(cmd);
}


// Set the left and right margin in the edit area, measured in pixels.
void wxStyledTextCtrl::SetMargins(int left, int right) {
    SetMarginLeft(left);
    SetMarginRight(right);
}


// Retrieve the start and end positions of the current selection.
void wxStyledTextCtrl::GetSelection(int* startPos, int* endPos) {
    if (startPos != NULL)
        *startPos = SendMsg(SCI_GETSELECTIONSTART);
    if (endPos != NULL)
        *endPos = SendMsg(SCI_GETSELECTIONEND);
}


// Retrieve the point in the window where a position is displayed.
wxPoint wxStyledTextCtrl::PointFromPosition(int pos) {
    int x = SendMsg(SCI_POINTXFROMPOSITION, 0, pos);
    int y = SendMsg(SCI_POINTYFROMPOSITION, 0, pos);
    return wxPoint(x, y);
}

// Scroll enough to make the given line visible
void wxStyledTextCtrl::ScrollToLine(int line) {
    m_swx->DoScrollToLine(line);
}


// Scroll enough to make the given column visible
void wxStyledTextCtrl::ScrollToColumn(int column) {
    m_swx->DoScrollToColumn(column);
}


bool wxStyledTextCtrl::SaveFile(const wxString& filename)
{
    wxFile file(filename, wxFile::write);

    if (!file.IsOpened())
        return FALSE;

    bool success = file.Write(GetText());

    if (success)
        SetSavePoint();

    return success;
}

bool wxStyledTextCtrl::LoadFile(const wxString& filename)
{
    wxFile file(filename, wxFile::read);

    if (!file.IsOpened())
        return FALSE;

    wxString contents;
    off_t len = file.Length();

    wxChar *buf = contents.GetWriteBuf(len);
    bool success = (file.Read(buf, len) == len);
    contents.UngetWriteBuf();

    if (success)
    {
        SetText(contents);
        EmptyUndoBuffer();
        SetSavePoint();
    }

    return success;
}


//----------------------------------------------------------------------
// Event handlers

void wxStyledTextCtrl::OnPaint(wxPaintEvent& evt) {
    wxPaintDC dc(this);
    m_swx->DoPaint(&dc, GetUpdateRegion().GetBox());
}

void wxStyledTextCtrl::OnScrollWin(wxScrollWinEvent& evt) {
    if (evt.GetOrientation() == wxHORIZONTAL)
        m_swx->DoHScroll(evt.GetEventType(), evt.GetPosition());
    else
        m_swx->DoVScroll(evt.GetEventType(), evt.GetPosition());
}

void wxStyledTextCtrl::OnScroll(wxScrollEvent& evt) {
    wxScrollBar* sb = wxDynamicCast(evt.GetEventObject(), wxScrollBar);
    if (sb) {
        if (sb->IsVertical())
            m_swx->DoVScroll(evt.GetEventType(), evt.GetPosition());
        else
            m_swx->DoHScroll(evt.GetEventType(), evt.GetPosition());
    }
}

void wxStyledTextCtrl::OnSize(wxSizeEvent& evt) {
    wxSize sz = GetClientSize();
    m_swx->DoSize(sz.x, sz.y);
}

void wxStyledTextCtrl::OnMouseLeftDown(wxMouseEvent& evt) {
    SetFocus();
    wxPoint pt = evt.GetPosition();
    m_swx->DoLeftButtonDown(Point(pt.x, pt.y), m_stopWatch.Time(),
                      evt.ShiftDown(), evt.ControlDown(), evt.AltDown());
}

void wxStyledTextCtrl::OnMouseMove(wxMouseEvent& evt) {
    wxPoint pt = evt.GetPosition();
    m_swx->DoLeftButtonMove(Point(pt.x, pt.y));
}

void wxStyledTextCtrl::OnMouseLeftUp(wxMouseEvent& evt) {
    wxPoint pt = evt.GetPosition();
    m_swx->DoLeftButtonUp(Point(pt.x, pt.y), m_stopWatch.Time(),
                      evt.ControlDown());
}


void wxStyledTextCtrl::OnMouseRightUp(wxMouseEvent& evt) {
    wxPoint pt = evt.GetPosition();
    m_swx->DoContextMenu(Point(pt.x, pt.y));
}


void wxStyledTextCtrl::OnMouseMiddleUp(wxMouseEvent& evt) {
    wxPoint pt = evt.GetPosition();
    m_swx->DoMiddleButtonUp(Point(pt.x, pt.y));
}

void wxStyledTextCtrl::OnContextMenu(wxContextMenuEvent& evt) {
    wxPoint pt = evt.GetPosition();
    ScreenToClient(&pt.x, &pt.y);
    m_swx->DoContextMenu(Point(pt.x, pt.y));
}


void wxStyledTextCtrl::OnMouseWheel(wxMouseEvent& evt) {
    m_swx->DoMouseWheel(evt.GetWheelRotation(),
                        evt.GetWheelDelta(),
                        evt.GetLinesPerAction(),
                        evt.ControlDown(),
                        evt.IsPageScroll());
}


void wxStyledTextCtrl::OnChar(wxKeyEvent& evt) {
    // On (some?) non-US keyboards the AltGr key is required to enter some
    // common characters.  It comes to us as both Alt and Ctrl down so we need
    // to let the char through in that case, otherwise if only ctrl or only
    // alt let's skip it.
    bool ctrl = evt.ControlDown();
    bool alt  = evt.AltDown();
    bool skip = ((ctrl || alt) && ! (ctrl && alt));

    int key = evt.GetKeyCode();

//     printf("OnChar key:%%d  consumed:%%d  ctrl:%%d  alt:%%d  skip:%%d\n",
//            key, m_lastKeyDownConsumed, ctrl, alt, skip);

    if ( (key <= WXK_START || key > WXK_NUMPAD_DIVIDE) &&
         !m_lastKeyDownConsumed && !skip) {
        m_swx->DoAddChar(key);
        return;
    }
    evt.Skip();
}


void wxStyledTextCtrl::OnKeyDown(wxKeyEvent& evt) {
    int key = evt.GetKeyCode();
    bool shift = evt.ShiftDown(),
         ctrl  = evt.ControlDown(),
         alt   = evt.AltDown();

    int processed = m_swx->DoKeyDown(key, shift, ctrl, alt, &m_lastKeyDownConsumed);

//     printf("KeyDn  key:%%d  shift:%%d  ctrl:%%d  alt:%%d  processed:%%d  consumed:%%d\n",
//            key, shift, ctrl, alt, processed, m_lastKeyDownConsumed);

    if (!processed && !m_lastKeyDownConsumed)
        evt.Skip();
}


void wxStyledTextCtrl::OnLoseFocus(wxFocusEvent& evt) {
    m_swx->DoLoseFocus();
}


void wxStyledTextCtrl::OnGainFocus(wxFocusEvent& evt) {
    m_swx->DoGainFocus();
}


void wxStyledTextCtrl::OnSysColourChanged(wxSysColourChangedEvent& evt) {
    m_swx->DoSysColourChange();
}


void wxStyledTextCtrl::OnEraseBackground(wxEraseEvent& evt) {
    // do nothing to help avoid flashing
}



void wxStyledTextCtrl::OnMenu(wxCommandEvent& evt) {
    m_swx->DoCommand(evt.GetId());
}


void wxStyledTextCtrl::OnListBox(wxCommandEvent& evt) {
    m_swx->DoOnListBox();
}


//----------------------------------------------------------------------
// Turn notifications from Scintilla into events


void wxStyledTextCtrl::NotifyChange() {
    wxStyledTextEvent evt(wxEVT_STC_CHANGE, GetId());
    evt.SetEventObject(this);
    GetEventHandler()->ProcessEvent(evt);
}


static void SetEventText(wxStyledTextEvent& evt, const char* text,
                         size_t length) {
    if(!text) return;

    // The unicode conversion MUST have a null byte to terminate the
    // string so move it into a buffer first and give it one.
    wxMemoryBuffer buf(length+1);
    buf.AppendData((void*)text, length);
    buf.AppendByte(0);
    evt.SetText(stc2wx(buf));
}


void wxStyledTextCtrl::NotifyParent(SCNotification* _scn) {
    SCNotification& scn = *_scn;
    wxStyledTextEvent evt(0, GetId());

    evt.SetEventObject(this);
    evt.SetPosition(scn.position);
    evt.SetKey(scn.ch);
    evt.SetModifiers(scn.modifiers);

    switch (scn.nmhdr.code) {
    case SCN_STYLENEEDED:
        evt.SetEventType(wxEVT_STC_STYLENEEDED);
        break;

    case SCN_CHARADDED:
        evt.SetEventType(wxEVT_STC_CHARADDED);
        break;

    case SCN_SAVEPOINTREACHED:
        evt.SetEventType(wxEVT_STC_SAVEPOINTREACHED);
        break;

    case SCN_SAVEPOINTLEFT:
        evt.SetEventType(wxEVT_STC_SAVEPOINTLEFT);
        break;

    case SCN_MODIFYATTEMPTRO:
        evt.SetEventType(wxEVT_STC_ROMODIFYATTEMPT);
        break;

    case SCN_KEY:
        evt.SetEventType(wxEVT_STC_KEY);
        break;

    case SCN_DOUBLECLICK:
        evt.SetEventType(wxEVT_STC_DOUBLECLICK);
        break;

    case SCN_UPDATEUI:
        evt.SetEventType(wxEVT_STC_UPDATEUI);
        break;

    case SCN_MODIFIED:
        evt.SetEventType(wxEVT_STC_MODIFIED);
        evt.SetModificationType(scn.modificationType);
        SetEventText(evt, scn.text, scn.length);
        evt.SetLength(scn.length);
        evt.SetLinesAdded(scn.linesAdded);
        evt.SetLine(scn.line);
        evt.SetFoldLevelNow(scn.foldLevelNow);
        evt.SetFoldLevelPrev(scn.foldLevelPrev);
        break;

    case SCN_MACRORECORD:
        evt.SetEventType(wxEVT_STC_MACRORECORD);
        evt.SetMessage(scn.message);
        evt.SetWParam(scn.wParam);
        evt.SetLParam(scn.lParam);
        break;

    case SCN_MARGINCLICK:
        evt.SetEventType(wxEVT_STC_MARGINCLICK);
        evt.SetMargin(scn.margin);
        break;

    case SCN_NEEDSHOWN:
        evt.SetEventType(wxEVT_STC_NEEDSHOWN);
        evt.SetLength(scn.length);
        break;

    case SCN_PAINTED:
        evt.SetEventType(wxEVT_STC_PAINTED);
        break;

    case SCN_USERLISTSELECTION:
        evt.SetEventType(wxEVT_STC_USERLISTSELECTION);
        evt.SetListType(scn.listType);
        SetEventText(evt, scn.text, strlen(scn.text));
        break;

    case SCN_URIDROPPED:
        evt.SetEventType(wxEVT_STC_URIDROPPED);
        SetEventText(evt, scn.text, strlen(scn.text));
        break;

    case SCN_DWELLSTART:
        evt.SetEventType(wxEVT_STC_DWELLSTART);
        evt.SetX(scn.x);
        evt.SetY(scn.y);
        break;

    case SCN_DWELLEND:
        evt.SetEventType(wxEVT_STC_DWELLEND);
        evt.SetX(scn.x);
        evt.SetY(scn.y);
        break;

    case SCN_ZOOM:
        evt.SetEventType(wxEVT_STC_ZOOM);
        break;

    case SCN_HOTSPOTCLICK:
        evt.SetEventType(wxEVT_STC_HOTSPOT_CLICK);
        break;

    case SCN_HOTSPOTDOUBLECLICK:
        evt.SetEventType(wxEVT_STC_HOTSPOT_DCLICK);
        break;

    case SCN_CALLTIPCLICK:
        evt.SetEventType(wxEVT_STC_CALLTIP_CLICK);
        break;

    default:
        return;
    }

    GetEventHandler()->ProcessEvent(evt);
}


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

wxStyledTextEvent::wxStyledTextEvent(wxEventType commandType, int id)
    : wxCommandEvent(commandType, id)
{
    m_position = 0;
    m_key = 0;
    m_modifiers = 0;
    m_modificationType = 0;
    m_length = 0;
    m_linesAdded = 0;
    m_line = 0;
    m_foldLevelNow = 0;
    m_foldLevelPrev = 0;
    m_margin = 0;
    m_message = 0;
    m_wParam = 0;
    m_lParam = 0;
    m_listType = 0;
    m_x = 0;
    m_y = 0;
    m_dragAllowMove = FALSE;
#if wxUSE_DRAG_AND_DROP
    m_dragResult = wxDragNone;
#endif
}

bool wxStyledTextEvent::GetShift() const { return (m_modifiers & SCI_SHIFT) != 0; }
bool wxStyledTextEvent::GetControl() const { return (m_modifiers & SCI_CTRL) != 0; }
bool wxStyledTextEvent::GetAlt() const { return (m_modifiers & SCI_ALT) != 0; }


wxStyledTextEvent::wxStyledTextEvent(const wxStyledTextEvent& event):
  wxCommandEvent(event)
{
    m_position =      event.m_position;
    m_key =           event.m_key;
    m_modifiers =     event.m_modifiers;
    m_modificationType = event.m_modificationType;
    m_text =          event.m_text;
    m_length =        event.m_length;
    m_linesAdded =    event.m_linesAdded;
    m_line =          event.m_line;
    m_foldLevelNow =  event.m_foldLevelNow;
    m_foldLevelPrev = event.m_foldLevelPrev;

    m_margin =        event.m_margin;

    m_message =       event.m_message;
    m_wParam =        event.m_wParam;
    m_lParam =        event.m_lParam;

    m_listType =     event.m_listType;
    m_x =            event.m_x;
    m_y =            event.m_y;

    m_dragText =     event.m_dragText;
    m_dragAllowMove =event.m_dragAllowMove;
#if wxUSE_DRAG_AND_DROP
    m_dragResult =   event.m_dragResult;
#endif
}

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









