OSDN Git Service

d2d3d0b62a16ad939b1e7b2873563fc17c0863cd
[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(FromFrameDIP(frame, 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                                 Ruby_showError(status);
365                         } else {
366                                 textCtrl->AppendText(wxT("-->"));
367                                 status = Ruby_showValue(val, &valueString);
368                                 if (status != 0) {
369                                         Ruby_showError(status);
370                                 } else {
371                                         AssignArray(&valueHistory, &nValueHistory, sizeof(char *), nValueHistory, &valueString);
372                                         if (nValueHistory >= MAX_HISTORY_LINES)
373                                                 DeleteArray(&valueHistory, &nValueHistory, sizeof(char *), 0, 1, NULL);
374                                 }
375                         }
376                         MyAppCallback_setConsoleColor(0);
377                         textCtrl->AppendText(wxT("\n"));
378                         MyAppCallback_showRubyPrompt();
379                 }
380         } else {
381                 textCtrl->AppendText(wxT("\n"));
382                 MyAppCallback_showRubyPrompt();
383         }
384         commandHistoryIndex = valueHistoryIndex = -1;
385 }
386
387 void
388 ConsoleFrame::ShowHistory(bool up, bool option)
389 {
390         char *p;
391         if (commandHistoryIndex == -1 && valueHistoryIndex == -1) {
392                 if (!up)
393                         return;
394                 historyPos = textCtrl->GetLastPosition();
395         }
396         if (option) {
397                 if (up) {
398                         if (valueHistoryIndex == -1) {
399                                 if (nValueHistory == 0)
400                                         return;
401                                 valueHistoryIndex = nValueHistory;
402                         }
403                         if (valueHistoryIndex <= 0)
404                                 return; /* Do nothing */
405                         valueHistoryIndex--;
406                         p = valueHistory[valueHistoryIndex];
407                 } else {
408                         if (valueHistoryIndex == -1)
409                                 return;  /*  Do nothing  */
410                         if (valueHistoryIndex == nValueHistory - 1) {
411                                 valueHistoryIndex = -1;
412                                 p = "";
413                         } else {
414                                 valueHistoryIndex++;
415                                 p = valueHistory[valueHistoryIndex];
416                         }
417                 }
418         } else {
419                 if (up) {
420                         if (commandHistoryIndex == -1) {
421                                 if (nCommandHistory == 0)
422                                         return;
423                                 commandHistoryIndex = nCommandHistory;
424                         }
425                         if (commandHistoryIndex <= 0)
426                                 return; /* Do nothing */
427                         commandHistoryIndex--;
428                         p = commandHistory[commandHistoryIndex];
429                 } else {
430                         if (commandHistoryIndex == -1)
431                                 return;  /*  Do nothing  */
432                         if (commandHistoryIndex == nCommandHistory - 1) {
433                                 commandHistoryIndex = -1;
434                                 p = "";
435                         } else {
436                                 commandHistoryIndex++;
437                                 p = commandHistory[commandHistoryIndex];
438                         }
439                 }
440         }
441         if (p == NULL)
442                 p = "";
443         textCtrl->Replace(historyPos, textCtrl->GetLastPosition(), wxT(""));
444         SetConsoleColor(option ? 1 : 3);
445         while (isspace(*p))
446                 p++;
447         textCtrl->AppendText(wxString(p, WX_DEFAULT_CONV));
448         SetConsoleColor(0);
449 }
450
451 void
452 ConsoleFrame::OnKeyDown(wxKeyEvent &event)
453 {
454         int code = event.GetKeyCode();
455
456 #if __WXOSX_COCOA__
457         //  in wxCocoa, the key down event is fired even when the input method still has
458         //  marked text. We need to avoid doing our own work when the input method is expecting
459         //  key events.
460         extern bool textCtrl_hasMarkedText();
461         if (textCtrl_hasMarkedText()) {
462                 event.Skip();
463                 return;
464         }
465 #endif
466         
467         if (code == WXK_RETURN || code == WXK_NUMPAD_ENTER)
468                 OnEnterPressed();
469         else if ((code == WXK_UP || code == WXK_DOWN) && (textCtrl->GetInsertionPoint() == textCtrl->GetLastPosition()))
470                 ShowHistory(code == WXK_UP, event.GetModifiers() == wxMOD_ALT);
471         else
472                 event.Skip();
473 }
474
475 void
476 ConsoleFrame::OnTextEnter(wxCommandEvent &event)
477 {
478         OnEnterPressed();
479 }
480
481 int
482 ConsoleFrame::AppendConsoleMessage(const char *mes)
483 {
484         wxString string(mes, WX_DEFAULT_CONV);
485         textCtrl->AppendText(string);
486         commandHistoryIndex = valueHistoryIndex = -1;
487         return string.Len();
488 }
489
490 void
491 ConsoleFrame::FlushConsoleMessage()
492 {
493         textCtrl->Refresh();
494         textCtrl->Update();
495 }
496
497 void
498 ConsoleFrame::SetConsoleColor(int color)
499 {
500         static const wxColour *col[4];
501         if (col[0] == NULL) {
502                 col[0] = wxBLACK;
503                 col[1] = wxRED;
504                 col[2] = wxGREEN;
505                 col[3] = wxBLUE;
506         }
507         current_attr->SetTextColour(*col[color % 4]);
508         current_attr->SetFont(*default_font);
509         textCtrl->SetDefaultStyle(wxTextAttr(*col[color % 4], wxNullColour, *default_font));
510 }
511
512 void
513 ConsoleFrame::EmptyBuffer(bool showRubyPrompt)
514 {
515         textCtrl->Clear();
516         if (showRubyPrompt)
517                 MyAppCallback_showRubyPrompt();
518         commandHistoryIndex = valueHistoryIndex = -1;
519 }
520
521 #pragma mark ====== Plain-C interface ======
522
523 int
524 MyAppCallback_showScriptMessage(const char *fmt, ...)
525 {
526     if (!gUseGUI) {
527         if (!gSuppressConsole) {
528             va_list ap;
529             va_start(ap, fmt);
530             return vprintf(fmt, ap);
531         } else return 0;
532     }
533         if (fmt != NULL) {
534                 char *p;
535                 va_list ap;
536                 int retval;
537                 va_start(ap, fmt);
538                 if (strchr(fmt, '%') == NULL) {
539                         /*  No format characters  */
540                         return wxGetApp().GetConsoleFrame()->AppendConsoleMessage(fmt);
541                 } else if (strcmp(fmt, "%s") == 0) {
542                         /*  Direct output of one string  */
543                         p = va_arg(ap, char *);
544                         return wxGetApp().GetConsoleFrame()->AppendConsoleMessage(p);
545                 }
546                 vasprintf(&p, fmt, ap);
547                 if (p != NULL) {
548                         retval = wxGetApp().GetConsoleFrame()->AppendConsoleMessage(p);
549                         free(p);
550                         return retval;
551                 } else return 0;
552         } else {
553                 wxGetApp().GetConsoleFrame()->FlushConsoleMessage();
554                 return 0;
555         }
556         return 0;
557 }
558
559 void
560 MyAppCallback_setConsoleColor(int color)
561 {
562     if (!gUseGUI)
563         return;
564         wxGetApp().GetConsoleFrame()->SetConsoleColor(color);
565 }
566
567 void
568 MyAppCallback_showRubyPrompt(void)
569 {
570         MyAppCallback_setConsoleColor(0);
571         MyAppCallback_showScriptMessage("%% ");
572 }