1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        steshell.cpp
3 // Purpose:     wxSTEditorShell
4 // Author:      John Labenski
5 // Modified by:
6 // Created:     11/05/2002
7 // RCS-ID:
8 // Copyright:   (c) John Labenski
9 // Licence:     wxWidgets licence
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 #include "precomp.h"
13 
14 #include "wx/stedit/stedit.h"
15 #include "wx/stedit/steshell.h"
16 
17 //-----------------------------------------------------------------------------
18 // wxSTEditorShell
19 //-----------------------------------------------------------------------------
IMPLEMENT_DYNAMIC_CLASS(wxSTEditorShell,wxSTEditor)20 IMPLEMENT_DYNAMIC_CLASS(wxSTEditorShell, wxSTEditor)
21 
22 BEGIN_EVENT_TABLE(wxSTEditorShell, wxSTEditor)
23     EVT_KEY_DOWN     (          wxSTEditorShell::OnKeyDown)
24     EVT_STC_UPDATEUI (wxID_ANY, wxSTEditorShell::OnSTCUpdateUI)
25 END_EVENT_TABLE()
26 
27 void wxSTEditorShell::Init()
28 {
29     m_line_history_index = 0;
30     m_max_history_lines  = 100;
31 
32     m_max_lines          = 10000; // arbitrary, seems reasonable
33     m_overflow_lines     = 2000;
34 
35     m_writeable_count    = 0;
36 }
37 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)38 bool wxSTEditorShell::Create(wxWindow *parent, wxWindowID id,
39                              const wxPoint& pos, const wxSize& size,
40                              long style, const wxString& name)
41 {
42     if (!wxSTEditor::Create(parent, id, pos, size, style, name))
43         return false;
44 
45     // set this up in case they don't want to bother with the preferences
46     SetMarginWidth(STE_MARGIN_NUMBER, 0);
47     SetMarginWidth(STE_MARGIN_FOLD,   0);
48     SetMarginWidth(PROMPT_MARGIN,     16);
49 
50     SetMarginType(PROMPT_MARGIN, wxSTC_MARGIN_SYMBOL);
51     SetMarginMask(PROMPT_MARGIN, 1<<PROMPT_MARKER);
52     // after creation you can change this to whatever prompt you prefer
53     MarkerDefine(PROMPT_MARKER, wxSTC_MARK_ARROWS, *wxBLACK, wxColour(255,255,0));
54     return true;
55 }
56 
~wxSTEditorShell()57 wxSTEditorShell::~wxSTEditorShell()
58 {
59 }
60 
AppendText(const wxString & text)61 void wxSTEditorShell::AppendText(const wxString &text)
62 {
63     BeginWriteable();                   // make it writeable
64 
65     wxSTEditor::AppendText(text);       // write the text
66     SetMaxLines(m_max_lines, m_overflow_lines); // check for line count overflow
67     GotoPos(GetLength());               // put cursor at end
68     EmptyUndoBuffer();                  // don't let them undo what you wrote!
69                                         //   but they can undo their own typing
70 
71     EndWriteable();                     // end the writeable state
72 }
73 
SetPromptText(const wxString & text)74 void wxSTEditorShell::SetPromptText(const wxString& text)
75 {
76     BeginWriteable();
77     int length      = GetLength();
78     int prompt_line = GetPromptLine();
79     int start_pos   = PositionFromLine(prompt_line);
80     SetTargetStart(start_pos);
81     SetTargetEnd(length);
82     ReplaceTarget(text);
83     GotoPos(GetLength());
84     EndWriteable();
85 }
86 
GetPromptText()87 wxString wxSTEditorShell::GetPromptText()
88 {
89     int prompt_line = GetPromptLine();
90     int start_pos   = PositionFromLine(prompt_line);
91     int end_pos     = GetLength();
92     wxString text(GetTextRange(start_pos, end_pos));
93     return text;
94 }
95 
BeginWriteable(bool make_writeable)96 void wxSTEditorShell::BeginWriteable(bool make_writeable)
97 {
98     m_writeable_count++;
99     if (make_writeable && GetReadOnly())
100         SetReadOnly(false);
101 }
EndWriteable(bool check_ro)102 void wxSTEditorShell::EndWriteable(bool check_ro)
103 {
104     if (m_writeable_count > 0)
105         m_writeable_count--;
106 
107     if (check_ro && (m_writeable_count == 0))
108         CheckReadOnly(true);
109 }
110 
GetPromptLine()111 int wxSTEditorShell::GetPromptLine()
112 {
113     int total_lines = GetLineCount();
114     return MarkerPrevious(total_lines+1, (1<<PROMPT_MARKER));
115 
116     // single line entry, return text on last line  FIXME - double check this
117     // Scintilla doesn't complain if you enter a line greater than the length to get last prompt
118     //int marker = MarkerGet(total_lines);
119     //if (((marker & (1<<PROMPT_MARKER)) != 0)
120     //{
121     //    text = GetLineText(total_lines); //.Strip(wxString::both);
122     //}
123     //else
124     //{
125     //    int marker_line = MarkerPrevious(total_lines+1, (1<<PROMPT_MARKER));
126     //}
127 }
128 
CaretOnPromptLine(STE_CaretPos_Type option)129 bool wxSTEditorShell::CaretOnPromptLine(STE_CaretPos_Type option)
130 {
131     int prompt_line = GetPromptLine();
132     bool on_last    = (GetCurrentLine() >= prompt_line);
133 
134     //wxPrintf(wxT("Caret on last line total %d current %d onlast %d\n"), total_lines, GetCurrentLine(), (int)on_last);
135 
136     if (!on_last && (option != STE_CARET_MOVE_NONE))
137     {
138         if ((option & STE_CARET_MOVE_LASTLINE) != 0)
139             GotoLine(prompt_line);
140         else if ((option & STE_CARET_MOVE_ENDTEXT) != 0)
141             GotoPos(GetLength());
142     }
143 
144     return GetCurrentLine() >= prompt_line;
145 }
146 
CheckReadOnly(bool set)147 bool wxSTEditorShell::CheckReadOnly(bool set)
148 {
149     bool make_ro = !CaretOnPromptLine(STE_CARET_MOVE_NONE);
150 
151     if (!make_ro)
152     {
153         // also check selection and make ro so they can't cut text not on last line
154         int prompt_line = GetPromptLine();
155         make_ro |= ((LineFromPosition(GetSelectionStart()) < prompt_line) ||
156                     (LineFromPosition(GetSelectionEnd())   < prompt_line));
157     }
158 
159     if (set && (make_ro != GetReadOnly()))
160         SetReadOnly(make_ro);
161 
162     return make_ro;
163 }
164 
CheckPrompt(bool set)165 bool wxSTEditorShell::CheckPrompt(bool set)
166 {
167     int total_lines = GetLineCount();
168     total_lines     = wxMax(0, total_lines-1);
169     bool has_prompt = (MarkerGet(total_lines) & (1<<PROMPT_MARKER)) != 0;
170 
171     if (set && !has_prompt)
172     {
173         MarkerAdd(total_lines, PROMPT_MARKER);
174         return true;
175     }
176 
177     return has_prompt;
178 }
179 
OnSTCUpdateUI(wxStyledTextEvent & event)180 void wxSTEditorShell::OnSTCUpdateUI(wxStyledTextEvent &event)
181 {
182     event.Skip();
183     if (m_writeable_count == 0)
184         CheckReadOnly(true);
185 }
186 
GetNextHistoryLine(bool forwards,const wxString & line)187 wxString wxSTEditorShell::GetNextHistoryLine(bool forwards, const wxString &line)
188 {
189     int count = (int)m_lineHistoryArray.GetCount();
190 
191     // no history, just return ""
192     if (count == 0)
193         return wxEmptyString;
194 
195     // return current one if it's different
196     if ((m_line_history_index >= 0) && (m_line_history_index < count) &&
197         (line != m_lineHistoryArray[m_line_history_index]))
198         return m_lineHistoryArray[m_line_history_index];
199 
200     if (forwards)
201     {
202         if (m_line_history_index >= count - 1)
203         {
204             m_line_history_index = (int)(count - 1); // fix it up
205             return wxEmptyString;
206         }
207 
208         m_line_history_index++;
209     }
210     else // reverse
211     {
212         if (m_line_history_index < 1) // already checked for empty array
213         {
214             m_line_history_index = 0; // fix it up
215             return wxEmptyString;
216         }
217 
218         m_line_history_index--;
219     }
220 
221     return m_lineHistoryArray[m_line_history_index];
222 }
223 
AddHistoryLine(const wxString & string,bool set_index_to_last)224 void wxSTEditorShell::AddHistoryLine(const wxString& string, bool set_index_to_last)
225 {
226     size_t count = m_lineHistoryArray.GetCount();
227 
228     // don't add same line twice
229     if ((count > 0) && (string == m_lineHistoryArray[count-1]))
230         return;
231 
232     m_lineHistoryArray.Add(string);
233     if (set_index_to_last)
234         m_line_history_index = (int)(m_lineHistoryArray.GetCount() - 1);
235 
236     SetMaxHistoryLines(GetMaxHistoryLines()); // remove any extra
237 }
238 
SetMaxHistoryLines(int max_lines)239 void wxSTEditorShell::SetMaxHistoryLines(int max_lines)
240 {
241     m_max_history_lines = max_lines;
242 
243     int extra = int(m_lineHistoryArray.GetCount()) - m_max_history_lines;
244     if ((m_max_history_lines >= 0) && (extra > 0))
245         m_lineHistoryArray.RemoveAt(0, extra);
246 
247     m_line_history_index = wxMin(m_line_history_index, int(m_lineHistoryArray.GetCount())-1);
248 }
249 
SetMaxLines(int max_lines,int overflow_lines)250 bool wxSTEditorShell::SetMaxLines(int max_lines, int overflow_lines)
251 {
252     m_max_lines      = max_lines;
253     m_overflow_lines = overflow_lines;
254     if (m_max_lines < 0) return false;
255 
256     int total_lines = GetLineCount();
257     total_lines     = wxMax(0, total_lines-1);
258 
259     // delete lines when more than m_max_lines, you'll eventually crash otherwise
260     if (total_lines > m_max_lines + m_overflow_lines)
261     {
262         BeginWriteable();
263 
264         int marker = MarkerGet(total_lines - m_max_lines);
265 
266         SetTargetStart(0);
267         SetTargetEnd(PositionFromLine(total_lines - m_max_lines));
268         ReplaceTarget(wxEmptyString);
269 
270         // wipe marker that has moved up if there shouldn't be a marker
271         if ((marker & (1<<PROMPT_MARKER)) == 0)
272             MarkerDelete(0, PROMPT_MARKER);
273 
274         EndWriteable();
275         return true;
276     }
277 
278     return false;
279 }
280 
OnKeyDown(wxKeyEvent & event)281 void wxSTEditorShell::OnKeyDown(wxKeyEvent &event)
282 {
283     // don't steal any keys from the autocomplete dropdown
284     if (AutoCompActive())
285     {
286         event.Skip(true);
287         return;
288     }
289 
290     event.Skip(false);
291     CheckReadOnly(true);
292 
293     switch (event.GetKeyCode())
294     {
295         case WXK_UP : case WXK_NUMPAD_UP :
296         {
297             // you can scroll up through multiline entry
298             int current_line = GetCurrentLine();
299             int prompt_line  = GetPromptLine();
300             if ((current_line < prompt_line) || (current_line > prompt_line))
301                 break;
302 
303             // up/down arrows go through the history buffer
304             wxString promptText = GetPromptText();
305             SetPromptText(GetNextHistoryLine(false, promptText));
306             return;
307         }
308         case WXK_DOWN : case WXK_NUMPAD_DOWN :
309         {
310             // you can scroll down through multiline entry
311             int total_lines  = GetLineCount();
312             total_lines      = wxMax(0, total_lines - 1);
313             int current_line = GetCurrentLine();
314             if (current_line < total_lines)
315                 break;
316 
317             // up/down arrows go through the history buffer
318             wxString promptText = GetPromptText();
319             SetPromptText(GetNextHistoryLine(true, promptText));
320             return;
321         }
322         case WXK_LEFT : case WXK_NUMPAD_LEFT :
323         {
324             int current_line = GetCurrentLine();
325             int prompt_line  = GetPromptLine();
326             if (current_line >= prompt_line)
327             {
328                 int caret_pos = 0;
329                 GetCurLine(&caret_pos);
330                 if (caret_pos < 1)
331                     return;
332             }
333             break;
334         }
335 
336         case WXK_PAGEUP   : case WXK_NUMPAD_PAGEUP   :
337         case WXK_PAGEDOWN : case WXK_NUMPAD_PAGEDOWN :
338         case WXK_END      : case WXK_NUMPAD_END   :
339         case WXK_HOME     : case WXK_NUMPAD_HOME  :
340         case WXK_RIGHT    : case WXK_NUMPAD_RIGHT :
341 
342         case WXK_SHIFT   :
343         case WXK_CONTROL :
344         case WXK_ALT     :
345         {
346             // default processing for these keys
347             event.Skip();
348             return;
349         }
350 
351         case WXK_RETURN : case WXK_NUMPAD_ENTER :
352         {
353             // put cursor at end if not already on the last line
354             if (!CaretOnPromptLine(STE_CARET_MOVE_NONE))
355             {
356                 GotoPos(GetLength());
357                 return;
358             }
359 
360             int current_line = GetCurrentLine();
361             int prompt_line  = GetPromptLine();
362 
363             // allow multiline entry for shift+enter
364             if ((current_line >= prompt_line) && event.ShiftDown())
365             {
366                 event.Skip();
367                 return;
368             }
369 
370             wxString promptText = GetPromptText();
371 
372             // goto the end of the line and store the line for the history
373             LineEnd();
374             if (promptText.Length())
375                 AddHistoryLine(promptText, true);
376 
377             // just send the event, the receiver can do what they like
378             SendEvent(wxEVT_STESHELL_ENTER, 0, GetState(), promptText);
379             return;
380         }
381         case WXK_BACK :
382         {
383             // go to the end of the last line if not on last line
384             if (!CaretOnPromptLine(STE_CARET_MOVE_NONE))
385             {
386                 GotoPos(GetLength());
387                 return;
388             }
389             // don't let them backspace into previous line
390             int caret_pos = 0;
391             GetCurLine(&caret_pos);
392             if (caret_pos < 1)
393                 return;
394 
395             break;
396         }
397         default : // move cursor to end if not already there
398         {
399             // reset history to start at most recent again
400             m_line_history_index = (int)(m_lineHistoryArray.GetCount() - 1);
401 
402             CaretOnPromptLine(STE_CARET_MOVE_ENDTEXT);
403             break;
404         }
405     }
406 
407     event.Skip();
408 }
409