sdk: const correctness
[sdk] / ecere / src / gui / drivers / NCursesInterface.ec
1 namespace gui::drivers;
2
3 import "instance"
4
5 #if (defined(__unix__) || defined(__APPLE__)) && !defined(__DOS__)
6
7 #undef __BLOCKS__
8 #define DBLCLICK_DELAY  0.3  // seconds
9 #define DBLCLICK_DELTA  1
10
11 // #define DEBUG_THREADS
12
13 #include <curses.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <sys/time.h>
17 #include <unistd.h>
18
19 import "Interface"
20
21 static class Message : struct
22 {
23    Message prev, next;
24    uint vmid;
25    int x, y;
26    Key key;
27 };
28
29 static OldList messages;
30
31 static Window nCursesDesktop;
32 static Point mousePosition;
33 static Box mouseRange;
34 static int caretX, caretY, caretVisible;
35
36 #define TIMEOUT (1000 / 18.2)
37
38 static Key characters2Key[256] =
39 {
40 // K_PERIOD - DEL
41    0,0,0,0,0,0,0,0,backSpace,tab,0,0,0,enter,0,0,
42    leftShift,leftControl,0,0,capsLock,0,0,0,0,0,0,escape,0,0,0,0,
43 //   space,pageUp,pageDown,end,home,left,up,right,down,keyPad5,0,0,0,insert,period,0,
44    space,k1,quote,k3,k4,k5,k7,quote,k9,k0,k8,equal,comma,minus,period,slash,
45    k0,k1,k2,k3,k4,k5,k6,k7,k8,k9,semicolon,semicolon,comma,equal,period,slash,
46    k2,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,
47    p,q,r,s,t,u,v,w,x,y,z,leftBracket,slash,rightBracket,k6,minus,
48    quote,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,
49    p,q,r,s,t,u,v,w,x,y,z,leftBracket,backSlash,rightBracket,quote,backSpace
50 };
51
52 static char * clipBoardData = null;
53
54 void ClearClipboard();
55
56 static Thread ncursesThread;
57 static Mutex ncursesMutex;
58 static bool ncursesTerminate;
59
60 /****************************************************************************
61    /// PRIVATE UTILITIES /////////////
62 ****************************************************************************/
63
64 static void AddMsg(uint vmid, int x, int y, Key key)
65 {
66    messages.Add(Message { vmid = vmid, x = x, y = y, key = key });
67 }
68
69 default:
70 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
71 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp;
72 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown;
73 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyHit;
74 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove;
75 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick;
76 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown;
77 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp;
78 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick;
79 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown;
80 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonUp;
81 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick;
82 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown;
83 extern int __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonUp;
84 private:
85
86 static void AddMessagesToQueue(int ch)
87 {
88    Key key = 0;
89    Key keyFlags = 0;
90    int ch1;
91 #ifdef NCURSES_VERSION
92    MEVENT event;
93 #endif
94
95    //timeout(0);
96    for(; ch != ERR; ch = getch())
97    {
98       if(ch == 0x1B) //Escaped = true;
99       {
100          ch = getch();
101 #ifdef NCDEBUG
102          fprintf(f, "  \\e: %d\n",ch);
103 #endif
104          if((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
105          {
106             key = characters2Key[ch];
107             keyFlags.alt = true;
108          }
109          else if(ch == '[')
110          {
111             ch = getch();
112 #ifdef NCDEBUG
113             fprintf(f, "   [: %d\n",ch);
114 #endif
115             ch1 = getch();
116 #ifdef NCDEBUG
117             fprintf(f, "   MOD: %c\n",ch1);
118 #endif
119             if(ch == '[')
120             {
121                if(ch1 == 'A') key = f1;
122                else if(ch1 == 'B') key = f2;
123                else if(ch1 == 'C') key = f3;
124                else if(ch1 == 'D') key = f4;
125                else if(ch1 == 'E') key = f5;
126             }
127             else
128             {
129                if(ch1 == '$') keyFlags.shift = true;
130                else if(ch1 == '^') keyFlags.ctrl = true;
131                else if(ch1 == '@') { keyFlags.shift = true; keyFlags.ctrl = true; }
132                else if(ch1 != ERR)
133                   ungetch(ch1);
134
135                switch(ch)
136                {
137                   case '1': case '7': key = home; break;
138                   case '2': key = insert; break;
139                   case '3': key = del; break;
140                   case '4': case '8': key = end; break;
141                   case '5': key = pageUp; break;
142                   case '6': key = pageDown; break;
143                   case 'a': keyFlags.shift = true; case 'A': key = up; break;
144                   case 'b': keyFlags.shift = true; case 'B': key = down; break;
145                   case 'c': keyFlags.shift = true; case 'C': key = right; break;
146                   case 'd': keyFlags.shift = true; case 'D': key = left; break;
147                   case 'k': keyFlags.shift = true; key = tab; break;
148                }
149             }
150             ch = 0;
151          }
152          else if(ch == 'O')
153          {
154             ch = getch();
155 #ifdef NCDEBUG
156             fprintf(f, "   O: %d\n",ch);
157 #endif
158             keyFlags.ctrl = true;
159             switch(ch)
160             {
161                case 'a': key = up; break;
162                case 'b': key = down; break;
163                case 'c': key = right; break;
164                case 'd': key = left; break;
165             }
166             ch = 0;
167          }
168          else
169          {
170             if(ch == ERR)
171             {
172                key = escape;
173                ch = 27;
174             }
175             else
176                ch = 0;
177             /*else
178             {
179                ungetch(ch);
180             }*/
181          }
182       }
183       else if(ch<256)
184       {
185          key = characters2Key[ch];
186          if(!key && ch <= 26)
187          {
188             keyFlags.ctrl = true;
189             key = characters2Key[ch + 'a' - 1];
190             ch += 'a' - 1;
191          }
192       }
193       else
194       {
195          switch(ch)
196          {
197             case KEY_ENTER: key = enter; break;
198             case KEY_BACKSPACE: key = backSpace; break;
199             case KEY_LEFT: key = left; break;
200             case KEY_RIGHT: key = right; break;
201             case KEY_UP: key = up; break;
202             case KEY_DOWN: key = down; break;
203             case KEY_SLEFT: key = left; keyFlags.shift = true; break;
204             case KEY_SRIGHT: key = right; keyFlags.shift = true; break;
205 //               case KEY_SUP: key = up; keyFlags.shift = true; break;
206 //               case KEY_SDOWN: key = down; keyFlags.shift = true; break;
207             case 353: key = tab; keyFlags.shift = true; break;
208             case 383:
209                keyFlags.shift = true;
210             case KEY_DC: key = del; break;
211             case KEY_IC: case KEY_EIC: key = insert; break;
212             case 362:
213             case KEY_HOME: key = home; break;
214             case 335:
215                keyFlags.ctrl = true;
216             case 385:
217             case KEY_END: key = end; break;
218             case KEY_SHOME: key = home; keyFlags.shift = true; break;
219             case KEY_SEND: key = end; keyFlags.shift = true; break;
220             case KEY_NPAGE: key = pageDown; break;
221             case KEY_PPAGE: key = pageUp; break;
222
223             // NCurses Extensions
224 #ifdef NCURSES_VERSION
225             case KEY_RESIZE:
226                resizeterm(LINES, COLS);
227                guiApp.SetDesktopPosition(0,0, COLS * textCellW, LINES * textCellH, true);
228                break;
229             case KEY_MOUSE:
230             {
231                static double lastTime[3];
232                static Point lastPos[3];
233                int x, y;
234                getmouse(&event);
235                x = Min(Max(event.x * textCellW, mouseRange.left), mouseRange.right);
236                y = Min(Max(event.y * textCellH, mouseRange.top), mouseRange.bottom);
237                if(mousePosition.x != x || mousePosition.y != y)
238                {
239                   mousePosition.x = x;
240                   mousePosition.y = y;
241
242                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMouseMove, x, y, key);
243                }
244                if(event.bstate & BUTTON1_PRESSED)
245                {
246                   Time time = GetTime();
247                   if(time - lastTime[0] < DBLCLICK_DELAY &&
248                      Abs(mousePosition.x - lastPos[0].x) < DBLCLICK_DELTA &&
249                      Abs(mousePosition.y - lastPos[0].y) < DBLCLICK_DELTA)
250                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick, x, y, keyFlags);
251                   else
252                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown, x, y, keyFlags);
253                   lastTime[0] = time;
254                   lastPos[0] = mousePosition;
255                }
256                if(event.bstate & BUTTON1_RELEASED)
257                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonUp, x, y, keyFlags);
258                if(event.bstate & BUTTON3_PRESSED)
259                {
260                   Time time = GetTime();
261                   if(time - lastTime[2] < DBLCLICK_DELAY &&
262                      Abs(mousePosition.x - lastPos[2].x) < DBLCLICK_DELTA &&
263                      Abs(mousePosition.y - lastPos[2].y) < DBLCLICK_DELTA)
264                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick, x, y, keyFlags);
265                   else
266                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown, x, y, keyFlags);
267                   lastTime[2] = time;
268                   lastPos[2] = mousePosition;
269                }
270                if(event.bstate & BUTTON3_RELEASED)
271                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonUp, x, y, keyFlags);
272                if(event.bstate & BUTTON2_PRESSED)
273                {
274                   Time time = GetTime();
275                   if(time - lastTime[1] < DBLCLICK_DELAY &&
276                      Abs(mousePosition.x - lastPos[1].x) < DBLCLICK_DELTA &&
277                      Abs(mousePosition.y - lastPos[1].y) < DBLCLICK_DELTA)
278                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick, x, y, keyFlags);
279                   else
280                      AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown, x, y, keyFlags);
281                   lastTime[1] = time;
282                   lastPos[1] = mousePosition;
283                }
284                if(event.bstate & BUTTON2_RELEASED)
285                   AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonUp,  x, y, keyFlags);
286                break;
287             }
288 #endif
289          }
290          if(ch >= KEY_F(1) && ch <= KEY_F(12))
291             key = (Key)f1 + ch-KEY_F(1);
292          if(ch >= KEY_F(13) && ch <= KEY_F(24))
293          {
294             key = (Key)f1 + ch-KEY_F(13);
295             keyFlags.shift = true;
296          }
297          ch = 0;
298       }
299       if(key)
300       {
301          key |= keyFlags;
302          AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown, ch, 1, key);
303          AddMsg(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp, ch, 0, key);
304       }
305    }
306 }
307
308 static uint NCursesThread(Thread thread)
309 {
310    while(!ncursesTerminate)
311    {
312       int ch;
313       ncursesMutex.Wait();
314       ch = getch();
315       if(ch != ERR)
316       {
317          AddMessagesToQueue(ch);
318          guiApp.SignalEvent();
319       }
320       ncursesMutex.Release();
321       if(ch == ERR)
322          Sleep(TIMEOUT / 1000.0);
323    }
324    return 0;
325 }
326
327 /****************************************************************************
328    /// DRIVER IMPLEMENTATION /////////////
329 ****************************************************************************/
330 class NCursesInterface : Interface
331 {
332    class_property(name) = "NCurses";
333
334    // --- User Interface System ---
335    bool Initialize()
336    {
337       static byte colorMap[8] = {0,4,2,6,1,5,3,7};
338
339       byte c = 0,f,b;
340       initscr();
341
342       SetLoggingMode(buffer, null);
343       if((bool)has_colors())
344       {
345          start_color();
346          for(b=0; b<8; b++)
347             for(f=0; f<8; f++)
348                init_pair(c++,colorMap[f],colorMap[b]);
349       }
350
351       printf( "\033(U\017");
352       fflush(stdout);
353       intrflush(stdscr, false);
354       nonl();
355       curs_set(false);
356       keypad(stdscr, true);
357    #ifdef NCURSES_VERSION
358       ESCDELAY = 0;
359       mousemask(REPORT_MOUSE_POSITION |
360                 BUTTON1_PRESSED | BUTTON1_RELEASED | BUTTON1_DOUBLE_CLICKED |
361                 BUTTON2_PRESSED | BUTTON2_RELEASED | BUTTON2_DOUBLE_CLICKED |
362                 BUTTON3_PRESSED | BUTTON3_RELEASED | BUTTON3_DOUBLE_CLICKED, null);
363       mouseinterval(0);
364    #endif
365       idlok(stdscr, false);
366       idcok(stdscr, false);
367       clearok(stdscr, false);
368       scrollok(stdscr, false);
369       cbreak();
370       caretVisible = false;
371       leaveok(stdscr, true);
372       timeout(0);
373       noecho();
374
375         ncursesMutex = Mutex { };
376
377       ncursesTerminate = false;
378
379       ncursesThread = Thread { };
380       incref ncursesThread;
381       ncursesThread.Main = NCursesThread;
382       ncursesThread.Create();
383
384       ncursesMutex.Wait();
385       return true;
386    }
387
388    void Terminate()
389    {
390       ncursesTerminate = true;
391       ncursesMutex.Release();
392       ncursesThread.Wait();
393       delete ncursesThread;
394       endwin();
395       delete ncursesMutex;
396       ClearClipboard();
397    }
398
399    bool ProcessInput(bool processAll)
400    {
401       Message msg;
402       bool result = false;
403
404       // Process messages
405       while(!ncursesTerminate && (msg = messages.first))
406       {
407          result = true;
408          messages.Remove(msg);
409          if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyDown || msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnKeyUp)
410             nCursesDesktop.KeyMessage(msg.vmid, msg.key, (char)msg.x);
411          else
412          {
413             if(nCursesDesktop.MouseMessage(msg.vmid, msg.x, msg.y, (Modifiers *)&msg.key, false, true))
414             {
415                if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftDoubleClick)
416                   nCursesDesktop.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnLeftButtonDown, msg.x, msg.y, (Modifiers *)&msg.key, false, false);
417                else if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightDoubleClick)
418                   nCursesDesktop.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnRightButtonDown, msg.x, msg.y, (Modifiers *)&msg.key, false, false);
419                else if(msg.vmid == __ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleDoubleClick)
420                   nCursesDesktop.MouseMessage(__ecereVMethodID___ecereNameSpace__ecere__gui__Window_OnMiddleButtonDown, msg.x, msg.y, (Modifiers *)&msg.key, false, false);
421             }
422          }
423          delete msg;
424
425          if(processAll && ncursesMutex)
426          {
427             ncursesMutex.Release();
428             ncursesMutex.Wait();
429          }
430          result = true;
431       }
432       if(ncursesTerminate)
433          messages.Free(null);
434       else if(caretVisible)
435       {
436          leaveok(stdscr, false);
437          move(caretY,caretX);
438          refresh();
439          curs_set(true);
440       }
441       return result;
442    }
443
444    void Wait()
445    {
446       ncursesMutex.Release();
447       guiApp.WaitEvent();
448       ncursesMutex.Wait();
449    }
450
451    void Lock(Window window)
452    {
453
454    }
455
456    void Unlock(Window window)
457    {
458
459    }
460
461    const char ** GraphicsDrivers(int * numDrivers)
462    {
463       static const char *graphicsDrivers[] = { "NCurses" };
464       *numDrivers = sizeof(graphicsDrivers) / sizeof(char *);
465       return (const char **)graphicsDrivers;
466    }
467
468    void GetCurrentMode(bool * fullScreen, Resolution * resolution, PixelFormat * colorDepth, int * refreshRate)
469    {
470       *fullScreen = true;
471       *colorDepth = pixelFormatText;
472    }
473
474    void EnsureFullScreen(bool *fullScreen)
475    {
476       *fullScreen = true;
477    }
478
479    void OffsetWindow(Window window, int * x, int * y)
480    {
481
482    }
483
484    bool ScreenMode(bool fullScreen, int resolution, int colorDepth, int refreshRate, bool * textMode)
485    {
486       if(fullScreen)
487       {
488          guiApp.SetDesktopPosition(0,0, COLS * textCellW, LINES * textCellH, false);
489          *textMode = true;
490          mouseRange.right = MAXINT;
491          mouseRange.bottom = MAXINT;
492            return true;
493       }
494       return false;
495    }
496
497    // --- Window Creation ---
498    void * CreateRootWindow(Window window)
499    {
500       nCursesDesktop = window;
501       return (void *) stdscr;
502    }
503
504    void DestroyRootWindow(void * windowHandle)
505    {
506    }
507
508    // -- Window manipulation ---
509
510    void SetRootWindowCaption(void * windowHandle, const char * name)
511    {
512    }
513
514    void PositionRootWindow(void * windowHandle, int x, int y, int w, int h, bool move, bool resize)
515    {
516    }
517
518    void OrderRootWindow(void * windowHandle, bool topMost)
519    {
520    }
521
522    void SetRootWindowColor(Window window)
523    {
524    }
525
526    void UpdateRootWindow(void * windowHandle)
527    {
528    }
529
530    void SetRootWindowState(Window window, int state, bool visible)
531    {
532
533    }
534
535    void ActivateRootWindow(void * windowHandle)
536    {
537    }
538
539    // --- Mouse-based window movement ---
540
541    void StartMoving(void * windowHandle, int x, int y, bool fromKeyBoard)
542    {
543    }
544
545    void StopMoving(void * windowHandle)
546    {
547    }
548
549    // -- Mouse manipulation ---
550
551    void GetMousePosition(int *x, int *y)
552    {
553       *x = mousePosition.x;
554       *y = mousePosition.y;
555    }
556
557    void SetMousePosition(int x, int y)
558    {
559       mousePosition.x = x;
560       mousePosition.y = y;
561    }
562
563    void SetMouseRange(Window window, Box box)
564    {
565       if(box != null)
566       {
567          mouseRange = box;
568       }
569       else
570       {
571          mouseRange.left = mouseRange.top = 0;
572          mouseRange.right = mouseRange.bottom = MAXINT;
573       }
574    }
575
576    void SetMouseCapture(void * windowHandle)
577    {
578
579    }
580
581    // -- Mouse cursor ---
582
583    void SetMouseCursor(Window window, int cursor)
584    {
585
586    }
587
588    // --- Caret ---
589
590    void SetCaret(int x, int y, int size)
591    {
592       if(size)
593       {
594          caretX = x / textCellW;
595          caretY = y / textCellH;
596          caretVisible = true;
597       }
598       else
599          caretVisible = false;
600    }
601
602    // --- Clipboard manipulation ---
603
604    void ClearClipboard()
605    {
606       delete clipBoardData;
607    }
608
609    bool AllocateClipboard(ClipBoard clipBoard, uint size)
610    {
611       bool result = false;
612       if((clipBoard.text = new char[size]))
613          result = true;
614       return result;
615    }
616
617    bool SaveClipboard(ClipBoard clipBoard)
618    {
619       bool result = false;
620       if(clipBoard.text)
621       {
622          delete clipBoardData;
623          clipBoardData = clipBoard.text;
624          clipBoard.text = null;
625          result = true;
626       }
627       return result;
628    }
629
630    bool LoadClipboard(ClipBoard clipBoard)
631    {
632       bool result = false;
633       if(clipBoardData)
634       {
635          clipBoard.text = clipBoardData;
636          result = true;
637       }
638       return result;
639    }
640
641    void UnloadClipboard(ClipBoard clipBoard)
642    {
643
644    }
645
646    // --- State based input ---
647
648    bool AcquireInput(void * windowHandle, bool state)
649    {
650       return false;
651    }
652
653    bool GetMouseState(MouseButtons * buttons, int * x, int * y)
654    {
655       bool result = false;
656
657       return result;
658    }
659
660    bool GetJoystickState(int device, Joystick joystick)
661    {
662       bool result = false;
663
664       return result;
665    }
666
667    bool GetKeyState(Key key)
668    {
669       return false;
670    }
671 }
672
673 #endif