OSDN Git Service

The text edit in MyListCtrl seems weird. Hopefully fixed.
[molby/Molby.git] / wxSources / ConsoleFrame.cpp
1 /*
2  *  ConsoleFrame.cpp
3  *  Molby
4  *
5  *  Created by Toshi Nagata on 08/10/27.
6  *  Copyright 2008 Toshi Nagata. All rights reserved.
7  *
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation version 2 of the License.
11  
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU General Public License for more details.
16  */
17
18 #include "wx/wxprec.h"
19
20 #ifndef WX_PRECOMP
21 #include "wx/wx.h"
22 #endif
23
24 #include "ConsoleFrame.h"
25
26 #include "wx/menu.h"
27 #include "wx/regex.h"
28 #include "wx/colour.h"
29 #include "wx/sizer.h"
30
31 #include "MyApp.h"
32 #include "../MolLib/Ruby_bind/Molby_extern.h"
33 #include "../MolLib/MolLib.h"
34 #include "../MolLib/Missing.h"
35 #include "MyMBConv.h"
36
37 #if defined(__WXMSW__)
38 #include <windows.h>
39 #include <richedit.h>
40 #endif
41
42 BEGIN_EVENT_TABLE(ConsoleFrame, wxFrame)
43         EVT_UPDATE_UI(-1, ConsoleFrame::OnUpdateUI)
44         EVT_CLOSE(ConsoleFrame::OnCloseWindow)
45         EVT_MENU(wxID_CLOSE, ConsoleFrame::OnClose)
46 /*      EVT_MENU(wxID_CUT, ConsoleFrame::OnCut)
47         EVT_MENU(wxID_COPY, ConsoleFrame::OnCopy)
48         EVT_MENU(wxID_PASTE, ConsoleFrame::OnPaste)
49         EVT_MENU(wxID_CLEAR, ConsoleFrame::OnClear) */
50         EVT_MENU(wxID_UNDO, ConsoleFrame::OnUndo)
51         EVT_MENU(wxID_REDO, ConsoleFrame::OnRedo)
52 END_EVENT_TABLE()
53
54 ConsoleFrame::ConsoleFrame(wxFrame *parent, const wxString& title, const wxPoint& pos, const wxSize& size, long type):
55         wxFrame(parent, wxID_ANY, title, pos, size, type)
56 {
57         valueHistory = commandHistory = NULL;
58         nValueHistory = nCommandHistory = 0;
59         valueHistoryIndex = commandHistoryIndex = -1;
60         keyInputPos = -1;
61         selectionFrom = selectionTo = -1;
62 }
63
64 ConsoleFrame::~ConsoleFrame()
65 {
66         int i;
67         for (i = 0; i < nValueHistory; i++)
68                 free(valueHistory[i]);
69         free(valueHistory);
70         for (i = 0; i < nCommandHistory; i++)
71                 free(commandHistory[i]);
72         free(commandHistory);   
73 //      wxGetApp().DocManager()->FileHistoryRemoveMenu(file_history_menu);
74 }
75
76 void
77 ConsoleFrame::OnCreate()
78 {
79         //  Make a text view
80         int width, height;
81
82         GetClientSize(&width, &height);
83         textCtrl = new wxTextCtrl(this, wxID_ANY, _T(""), wxPoint(0, 0), wxSize(100, 100), wxTE_MULTILINE | wxTE_RICH | wxTE_PROCESS_ENTER
84                                                           );
85
86 #if defined(__WXMSW__)
87         {
88                 HWND hwnd = (HWND)(textCtrl->GetHWND());
89                 DWORD dwOptions;
90                 LPARAM result;
91                 
92                 /*  Disable dual language mode  */
93                 dwOptions = SendMessage(hwnd, EM_GETLANGOPTIONS, 0, 0); 
94                 dwOptions &= ~0x02;   /*  0x02 = IMF_AUTOFONT  */
95                 result = SendMessage(hwnd, EM_SETLANGOPTIONS, 0, (LPARAM)dwOptions);
96                 printf("%ld\n", (long)result);          
97         }
98 #endif
99                 
100         wxBoxSizer *consoleSizer = new wxBoxSizer(wxHORIZONTAL);
101         consoleSizer->Add(textCtrl, 1, wxEXPAND);
102         this->SetSizer(consoleSizer);
103         consoleSizer->SetSizeHints(this);
104         
105         //  Set the default font (fixed-pitch)
106 #if defined(__WXMSW__)
107         default_font = new wxFont(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
108         if (!default_font->SetFaceName(wxT("Consolas")))
109                 default_font->SetFaceName(wxT("Courier"));
110 #elif defined(__WXMAC__)
111         default_font = new wxFont(11, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
112         default_font->SetFaceName(wxT("Monaco"));
113 #else
114         default_font = new wxFont(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
115 #endif
116         current_attr = new wxTextAttr;
117         current_attr->SetFont(*default_font);
118         textCtrl->SetDefaultStyle(*current_attr);
119
120         //  Connect textCtrl event handler
121         textCtrl->Connect(-1, wxEVT_KEY_DOWN, wxKeyEventHandler(ConsoleFrame::OnKeyDown), NULL, this);
122         textCtrl->Connect(-1, wxEVT_TEXT_ENTER, wxCommandEventHandler(ConsoleFrame::OnTextEnter), NULL, this);
123         textCtrl->Connect(-1, wxEVT_SET_FOCUS, wxFocusEventHandler(ConsoleFrame::OnSetFocus), NULL, this);
124         textCtrl->Connect(-1, wxEVT_KILL_FOCUS, wxFocusEventHandler(ConsoleFrame::OnKillFocus), NULL, this);
125
126         wxMenuBar *menu_bar = wxGetApp().CreateMenuBar(2, &file_history_menu, &edit_menu);
127         
128         //// Associate the menu bar with the frame
129         SetMenuBar(menu_bar);
130 }
131
132 ConsoleFrame *
133 ConsoleFrame::CreateConsoleFrame(wxFrame *parent)
134 {
135 #ifdef __WXMSW__
136         wxPoint origin(0, 0);
137         wxSize size(640, 200);
138 #else
139         wxPoint origin(10, 24);
140         wxSize size(640, 200);
141 #endif
142         ConsoleFrame *frame = new ConsoleFrame(parent, _T("Molby Console"), origin, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE);
143
144         frame->OnCreate();
145         frame->SetClientSize(size);
146         
147         return frame;
148 }
149
150 bool
151 GetLineIncludingPosition(wxTextCtrl *ctrl, int pos, int *start, int *end)
152 {
153         int pos1, pos2, posend;
154         wxChar posChar;
155         
156         if (ctrl == NULL)
157                 return false;
158         if (pos == 0)
159                 pos1 = 0;
160         else {
161                 pos1 = pos;
162                 while (pos1 > 0) {
163                         posChar = ctrl->GetRange(pos1 - 1, pos1).GetChar(0);
164                         if (posChar == '\n')
165                                 break;
166                         pos1--;
167                 }
168         }
169         posend = ctrl->GetLastPosition();
170         posChar = ctrl->GetRange(posend - 1, posend).GetChar(0);
171         if (pos >= posend)
172                 pos2 = pos;
173         else {
174                 pos2 = pos;
175                 while (pos2 < posend) {
176                         posChar = ctrl->GetRange(pos2, pos2 + 1).GetChar(0);
177                         if (posChar == '\n') {
178                                 pos2++;
179                                 break;
180                         }
181                         pos2++;
182                 }
183         }
184         if (start != NULL)
185                 *start = pos1;
186         if (end != NULL)
187                 *end = pos2;
188         return true;
189 }
190
191 void
192 ConsoleFrame::OnCloseWindow(wxCloseEvent &event)
193 {
194         //  Do not delete this window; it may be reopened later
195         this->Hide();
196         //  Check if all windows are gone
197         wxGetApp().CheckIfAllWindowsAreGone(NULL);
198 }
199
200 void
201 ConsoleFrame::OnClose(wxCommandEvent &event)
202 {
203         this->Close();
204 }
205
206 void
207 ConsoleFrame::OnKillFocus(wxFocusEvent &event)
208 {
209 #if defined(__WXMSW__)
210         textCtrl->GetSelection(&selectionFrom, &selectionTo);
211 #endif
212         event.Skip();
213 }
214
215 void
216 ConsoleFrame::OnSetFocus(wxFocusEvent &event)
217 {
218 #if defined(__WXMSW__)
219         if (selectionFrom >= 0 && selectionTo >= 0) {
220                 textCtrl->SetSelection(selectionFrom, selectionTo);
221         }
222 #endif
223         event.Skip();
224 }
225
226 //  I do not understand why these functions should be written...
227 //  Certainly there should be better way to implement these.
228 void
229 ConsoleFrame::OnUndo(wxCommandEvent &event)
230 {
231         if (wxWindow::FindFocus() == textCtrl)
232                 textCtrl->Undo();
233         else event.Skip();
234 }
235
236 void
237 ConsoleFrame::OnRedo(wxCommandEvent &event)
238 {
239         if (wxWindow::FindFocus() == textCtrl)
240                 textCtrl->Redo();
241         else event.Skip();
242 }
243
244 void
245 ConsoleFrame::OnUpdateUI(wxUpdateUIEvent& event)
246 {
247         int uid = event.GetId();
248         if (uid == wxID_CLOSE)
249                 //  Why this is not automatically done??
250                 event.Enable(true);
251         else if (uid == wxID_UNDO || uid == wxID_REDO) {
252                 if (wxWindow::FindFocus() == textCtrl) {
253                         if (uid == wxID_UNDO)
254                                 event.Enable(textCtrl->CanUndo());
255                         else
256                                 event.Enable(textCtrl->CanRedo());
257                 } else event.Skip();
258         } else event.Skip();
259 }
260
261 void
262 ConsoleFrame::OnEnterPressed()
263 {
264         int start, pos, end, veryend, lastpos;
265         wxChar startChar;
266         
267         if (::wxGetKeyState(WXK_ALT)) {
268                 textCtrl->WriteText(wxT("\n> "));
269                 return;
270         }
271         
272         pos = textCtrl->GetInsertionPoint();
273         lastpos = textCtrl->GetLastPosition();
274         
275         veryend = -1;
276         while (pos >= 0) {
277                 if (!GetLineIncludingPosition(textCtrl, pos, &start, &end) || start == end) {
278                         start = end = veryend = pos;
279                         break;
280                 }
281                 if (veryend < 0)
282                         veryend = end;
283                 startChar = textCtrl->GetRange(start, start + 1).GetChar(0);
284                 if (startChar == '%') {
285                         start++;
286                         break;
287                 } else if (startChar == '>') {
288                         pos = start - 1;
289                         continue;
290                 } else {
291                         start = end = veryend = pos;
292                         break;
293                 }
294         }
295         while (start < end && veryend < lastpos) {
296                 pos = veryend + 1;
297                 if (!GetLineIncludingPosition(textCtrl, pos, &pos, &end) || pos == end) {
298                         break;
299                 }
300                 startChar = textCtrl->GetRange(pos, pos + 1).GetChar(0);
301                 if (startChar != '>')
302                         break;
303                 veryend = end;
304         }
305
306         wxString string = textCtrl->GetRange(start, veryend);
307         int len = string.Len();
308         
309         //  Is there any non-whitespace characters?
310         wxChar ch;
311         int i;
312         for (i = 0; i < len; i++) {
313                 ch = string[i];
314                 if (ch != ' ' && ch != '\t' && ch != '\n' && ch != 'r')
315                         break;
316         }
317         if (i < len) {
318                 //  Input is not empty
319                 if (veryend < lastpos) {
320                         // Enter is pressed in the block not at the end
321                         // -> Insert the text at the end
322                         wxRegEx re1(wxT("[ \t\n]*$"));
323                         re1.ReplaceFirst(&string, wxT(""));
324                         wxRegEx re2(wxT("^[ \t\n]*"));
325                         re2.ReplaceFirst(&string, wxT(""));
326                         if (textCtrl->GetRange(lastpos - 1, lastpos).GetChar(0) != '\n')
327                                 textCtrl->AppendText(wxT("\n"));
328                         MyAppCallback_showRubyPrompt();
329                         MyAppCallback_setConsoleColor(3);
330                         textCtrl->AppendText(string);
331                 } else {
332                         wxTextAttr scriptAttr(*wxBLUE, wxNullColour, *default_font);
333                         textCtrl->SetStyle(start, veryend, scriptAttr);
334                 }
335                 string.Append(wxT("\n"));  //  To avoid choking Ruby interpreter
336                 wxRegEx re3(wxT("\n>"));
337                 re3.Replace(&string, wxT("\n"));
338                 ch = textCtrl->GetRange(lastpos - 1, lastpos).GetChar(0);
339                 if (ch != '\n')
340                         textCtrl->AppendText(wxT("\n"));
341                 textCtrl->Update();
342                 MyAppCallback_setConsoleColor(0);
343                 
344                 //  Invoke ruby interpreter
345                 int status;
346                 RubyValue val;
347                 char *script;
348                 Molecule *mol = MoleculeCallback_currentMolecule();
349                 int lock_needed = (mol != NULL && mol->mutex != NULL);
350                 if (lock_needed)
351                         MoleculeLock(mol);
352                 script = strdup(string.mb_str(WX_DEFAULT_CONV));
353                 val = Molby_evalRubyScriptOnMolecule(script, MoleculeCallback_currentMolecule(), NULL, &status);
354                 if (lock_needed)
355                         MoleculeUnlock(mol);
356                 script[strlen(script) - 1] = 0;  /*  Remove the last newline  */
357                 AssignArray(&commandHistory, &nCommandHistory, sizeof(char *), nCommandHistory, &script);
358                 if (nCommandHistory >= MAX_HISTORY_LINES)
359                         DeleteArray(&commandHistory, &nCommandHistory, sizeof(char *), 0, 1, NULL);
360                 if (status != -1) {  /*  Status -1 is already handled  */
361                         char *valueString;
362                         MyAppCallback_setConsoleColor(1);
363                         if (status != 0) {
364                                 Molby_showError(status);
365                         } else {
366                                 textCtrl->AppendText(wxT("-->"));
367                                 Molby_showRubyValue(val, &valueString);
368                                 AssignArray(&valueHistory, &nValueHistory, sizeof(char *), nValueHistory, &valueString);
369                                 if (nValueHistory >= MAX_HISTORY_LINES)
370                                         DeleteArray(&valueHistory, &nValueHistory, sizeof(char *), 0, 1, NULL);
371                         }
372                         MyAppCallback_setConsoleColor(0);
373                         textCtrl->AppendText(wxT("\n"));
374                         MyAppCallback_showRubyPrompt();
375                 }
376         } else {
377                 textCtrl->AppendText(wxT("\n"));
378                 MyAppCallback_showRubyPrompt();
379         }
380         commandHistoryIndex = valueHistoryIndex = -1;
381 }
382
383 void
384 ConsoleFrame::ShowHistory(bool up, bool option)
385 {
386         char *p;
387         if (commandHistoryIndex == -1 && valueHistoryIndex == -1) {
388                 if (!up)
389                         return;
390                 historyPos = textCtrl->GetLastPosition();
391         }
392         if (option) {
393                 if (up) {
394                         if (valueHistoryIndex == -1) {
395                                 if (nValueHistory == 0)
396                                         return;
397                                 valueHistoryIndex = nValueHistory;
398                         }
399                         if (valueHistoryIndex <= 0)
400                                 return; /* Do nothing */
401                         valueHistoryIndex--;
402                         p = valueHistory[valueHistoryIndex];
403                 } else {
404                         if (valueHistoryIndex == -1)
405                                 return;  /*  Do nothing  */
406                         if (valueHistoryIndex == nValueHistory - 1) {
407                                 valueHistoryIndex = -1;
408                                 p = "";
409                         } else {
410                                 valueHistoryIndex++;
411                                 p = valueHistory[valueHistoryIndex];
412                         }
413                 }
414         } else {
415                 if (up) {
416                         if (commandHistoryIndex == -1) {
417                                 if (nCommandHistory == 0)
418                                         return;
419                                 commandHistoryIndex = nCommandHistory;
420                         }
421                         if (commandHistoryIndex <= 0)
422                                 return; /* Do nothing */
423                         commandHistoryIndex--;
424                         p = commandHistory[commandHistoryIndex];
425                 } else {
426                         if (commandHistoryIndex == -1)
427                                 return;  /*  Do nothing  */
428                         if (commandHistoryIndex == nCommandHistory - 1) {
429                                 commandHistoryIndex = -1;
430                                 p = "";
431                         } else {
432                                 commandHistoryIndex++;
433                                 p = commandHistory[commandHistoryIndex];
434                         }
435                 }
436         }
437         textCtrl->Replace(historyPos, textCtrl->GetLastPosition(), wxT(""));
438         SetConsoleColor(option ? 1 : 3);
439         while (isspace(*p))
440                 p++;
441         textCtrl->AppendText(wxString(p, WX_DEFAULT_CONV));
442         SetConsoleColor(0);
443 }
444
445 void
446 ConsoleFrame::OnKeyDown(wxKeyEvent &event)
447 {
448         int code = event.GetKeyCode();
449
450 #if __WXOSX_COCOA__
451         //  in wxCocoa, the key down event is fired even when the input method still has
452         //  marked text. We need to avoid doing our own work when the input method is expecting
453         //  key events.
454         extern bool textCtrl_hasMarkedText();
455         if (textCtrl_hasMarkedText()) {
456                 event.Skip();
457                 return;
458         }
459 #endif
460         
461         if (code == WXK_RETURN || code == WXK_NUMPAD_ENTER)
462                 OnEnterPressed();
463         else if ((code == WXK_UP || code == WXK_DOWN) && (textCtrl->GetInsertionPoint() == textCtrl->GetLastPosition()))
464                 ShowHistory(code == WXK_UP, event.GetModifiers() == wxMOD_ALT);
465         else
466                 event.Skip();
467 }
468
469 void
470 ConsoleFrame::OnTextEnter(wxCommandEvent &event)
471 {
472         OnEnterPressed();
473 }
474
475 int
476 ConsoleFrame::AppendConsoleMessage(const char *mes)
477 {
478         wxString string(mes, WX_DEFAULT_CONV);
479         textCtrl->AppendText(string);
480         commandHistoryIndex = valueHistoryIndex = -1;
481         return string.Len();
482 }
483
484 void
485 ConsoleFrame::FlushConsoleMessage()
486 {
487         textCtrl->Refresh();
488         textCtrl->Update();
489 }
490
491 void
492 ConsoleFrame::SetConsoleColor(int color)
493 {
494         static const wxColour *col[4];
495         if (col[0] == NULL) {
496                 col[0] = wxBLACK;
497                 col[1] = wxRED;
498                 col[2] = wxGREEN;
499                 col[3] = wxBLUE;
500         }
501         current_attr->SetTextColour(*col[color % 4]);
502         current_attr->SetFont(*default_font);
503         textCtrl->SetDefaultStyle(wxTextAttr(*col[color % 4], wxNullColour, *default_font));
504 }
505
506 void
507 ConsoleFrame::EmptyBuffer(bool showRubyPrompt)
508 {
509         textCtrl->Clear();
510         if (showRubyPrompt)
511                 MyAppCallback_showRubyPrompt();
512         commandHistoryIndex = valueHistoryIndex = -1;
513 }
514
515 #pragma mark ====== Plain-C interface ======
516
517 int
518 MyAppCallback_showScriptMessage(const char *fmt, ...)
519 {
520         if (fmt != NULL) {
521                 char *p;
522                 va_list ap;
523                 int retval;
524                 va_start(ap, fmt);
525                 if (strchr(fmt, '%') == NULL) {
526                         /*  No format characters  */
527                         return wxGetApp().GetConsoleFrame()->AppendConsoleMessage(fmt);
528                 } else if (strcmp(fmt, "%s") == 0) {
529                         /*  Direct output of one string  */
530                         p = va_arg(ap, char *);
531                         return wxGetApp().GetConsoleFrame()->AppendConsoleMessage(p);
532                 }
533                 vasprintf(&p, fmt, ap);
534                 if (p != NULL) {
535                         retval = wxGetApp().GetConsoleFrame()->AppendConsoleMessage(p);
536                         free(p);
537                         return retval;
538                 } else return 0;
539         } else {
540                 wxGetApp().GetConsoleFrame()->FlushConsoleMessage();
541                 return 0;
542         }
543         return 0;
544 }
545
546 void
547 MyAppCallback_setConsoleColor(int color)
548 {
549         wxGetApp().GetConsoleFrame()->SetConsoleColor(color);
550 }
551
552 void
553 MyAppCallback_showRubyPrompt(void)
554 {
555         MyAppCallback_setConsoleColor(0);
556         MyAppCallback_showScriptMessage("%% ");
557 }