// Purpose: input line with history (wxTextCtrl+wxSpinButton)
// Copyright: (c) 2007 Marcin Wojdyr 
// Licence: wxWidgets licence 
// $Id: inputline.cpp 266 2007-03-03 01:49:39Z wojdyr $

/// Input line with history (wxTextCtrl+wxSpinButton). 
/// Supported keybindings:
///      up / Ctrl-p   Move `back' through the history list, 
///                     fetching the previous command.
///    down / Ctrl-n   Move `forward' through the history list, 
///                     fetching the next command.
///      page up       Move to the first line in the history.
///      page down     Move to the end of the input history, 
///                     i.e., the line currently being entered.
///           Ctrl-a   Move to the start of the line.
///           Ctrl-e   Move to the end of the line.
///           Ctrl-k   Cut the text from the current cursor position 
///                     to the end of the line.
///           Ctrl-u   Cut backward from the cursor to the beginning 
///                     of the current line.
///           Ctrl-y   Yank, the same as Ctrl-V
/// This control was originally written for Fityk (http://fityk.sf.net)

#include <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include "inputline.h"


BEGIN_EVENT_TABLE(InputLine, wxPanel)
    EVT_SPIN(-1, InputLine::OnSpinButton)
    //KEY_DOWN events from wxTextCtrl and wxSpinButtonare Connect()-ed
END_EVENT_TABLE()

InputLine::InputLine(wxWindow *parent, wxWindowID id, 
                     V1Callback<wxString const&> const& receiver)
    : wxPanel(parent, id), m_hpos(0), m_receiver(receiver)
{
    m_text = new wxTextCtrl(this, wxID_ANY, wxT(""),
                       wxDefaultPosition, wxDefaultSize, 
                       wxWANTS_CHARS|wxTE_PROCESS_ENTER /*|wxTE_PROCESS_TAB*/);
    m_button = new wxSpinButton(this, wxID_ANY, 
                                wxDefaultPosition, wxDefaultSize,
                                wxSP_VERTICAL|wxSP_ARROW_KEYS|wxNO_BORDER),
    m_button->SetRange(0, 0); 
    m_button->SetValue(0); 
    wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
    sizer->Add(m_text, 1, wxEXPAND);
    sizer->Add(m_button, 0, wxEXPAND);
    SetSizer(sizer);
    SetMinSize(wxSize(-1, m_text->GetBestSize().y));
    m_history.Add(wxT(""));
    m_text->Connect(wxID_ANY, wxEVT_KEY_DOWN, 
                    wxKeyEventHandler(InputLine::OnKeyDownAtText), 
                    0, this);
    m_button->Connect(wxID_ANY, wxEVT_KEY_DOWN, 
                      wxKeyEventHandler(InputLine::OnKeyDownAtSpinButton), 
                      0, this);
}

wxSize InputLine::DoGetBestSize() const
{
    return wxSize(wxPanel::DoGetBestSize().x, m_text->GetMinSize().y);
}

void InputLine::RedirectKeyPress(wxKeyEvent& event)
{
    m_text->SetFocus();
    m_text->SetInsertionPointEnd();
    m_text->EmulateKeyPress(event);
}

void InputLine::HistoryMove(int n, bool relative)
{
    int new_pos = relative ? m_hpos + n : n;
    if (!relative && n < 0)
        new_pos += m_history.GetCount();
    if (new_pos == m_hpos || new_pos<0 || new_pos >= (int)m_history.GetCount()) 
        return;

    // save line editing
    m_history[m_hpos] = m_text->GetValue();

    m_hpos = new_pos;
    m_text->SetValue(m_history[new_pos]);
    m_button->SetValue(m_history.GetCount() - 1 - new_pos);
    m_text->SetFocus();
    m_text->SetInsertionPointEnd();
}

void InputLine::OnInputLine(const wxString& line) 
{ 
    m_history.Last() = line;
    m_history.Add(wxT(""));
    if (m_history.GetCount() > 1024+128)
        m_history.RemoveAt(0, 128);
    m_hpos = m_history.GetCount() - 1;
    m_button->SetRange(0, m_hpos);
    m_button->SetValue(0);
    m_receiver(line); 
    m_text->SetFocus();
}

void InputLine::OnKeyDownAtText (wxKeyEvent& event)
{
    if (event.ControlDown()) {
        switch (event.m_keyCode) {
            case 'A':
                m_text->SetInsertionPoint(0);
                return;
            case 'E':
                m_text->SetInsertionPointEnd();
                return;
            case 'P':
                HistoryMove(-1, true);
                return;
            case 'N':
                HistoryMove(+1, true);
                return;
            case 'K':
                m_text->SetSelection(m_text->GetInsertionPoint(), 
                                     m_text->GetLastPosition());
                m_text->Cut();
                return;
            case 'U':
                m_text->SetSelection(0, m_text->GetInsertionPoint());
                m_text->Cut();
                return;
            case 'Y':
                m_text->Paste();
                return;
        }
    }
    switch (event.m_keyCode) {
        case WXK_RETURN:
        case WXK_NUMPAD_ENTER:
            {
            wxString s = m_text->GetValue().Trim();
            if (!s.IsEmpty())
                OnInputLine(s);
            m_text->Clear();
            }
            break;
        // to process WXK_TAB, you need to uncomment wxTE_PROCESS_TAB
        //case WXK_TAB:
        //    Navigate();
        //    break;
        case WXK_UP:
        case WXK_NUMPAD_UP:
            HistoryMove(-1, true);
            break;
        case WXK_DOWN:
        case WXK_NUMPAD_DOWN:
            HistoryMove(+1, true);
            break;
        case WXK_PAGEUP:
        case WXK_NUMPAD_PAGEUP:
            HistoryMove(0, false);
            break;
        case WXK_PAGEDOWN:
        case WXK_NUMPAD_PAGEDOWN:
            HistoryMove(-1, false);
            break;
        default:
            event.Skip();
    }
}

void InputLine::OnKeyDownAtSpinButton(wxKeyEvent& event)
{
    RedirectKeyPress(event);
    event.Skip();
}



syntax highlighted by Code2HTML, v. 0.9.1