-
Notifications
You must be signed in to change notification settings - Fork 0
/
EngageWX.cpp
9095 lines (8883 loc) · 332 KB
/
EngageWX.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// PREAMBLE: Modal UI Toolkit for wxWidgets
// Modal GUIs do not layout the screen spatially using windows.
// Instead they time-slice the app's interaction time
// into "modes of operation".
// Each mode of operation paints to the entire screen
// and has exclusive control of kybd input
// which is the only form of input needed in a Modal app.
//
// A mode of operation defines the GUI's behavior
// in terms of its visual response to kybd inputs.
// Modes can be loaded into and unloaded from the GUI
// via a mode manager that both manages the modes of the Modal GUI
// and interfaces with the wxWidgets wxWindow class.
// At any given time there is a "primary" mode operational
// reflecting the primary activity that the user is involved in
// within a primary mode's operational context (time-slice)
// transient "accessory" modes may be popped up
// to enable the collection of some additional input from the user
// or to switch the primary mode to a different one.
//
// The toolkit defines an abstract base mode struct
// and a handful of generally useful, accessory mode structs,
// This file implements a primary mode called SModeSrcEditor
// for editing and navigating a Modal C source-code file.
// This serves as a template for an app designer
// to produce their own mode in a Modal app.
//
// THE SOURCE EDITOR MODE : A TEMPLATE FOR DESIGNING A MODE
// The approach we have taken in the design of this source-code editor
// is to target the narrow domain of Modal source-code.
// By design, all of a Modal app's source-code is contained in a single .cpp file.
// This is unlike a typical codebase
// that has several different .cpp files with associated .h files.
// Instead of several small .cpp files that are navigated using a project directory browser,
// we use a large single .cpp file that contains a large number of functions.
// To navigate this large set of C functions (this is a 9000+ line codefile),
// we use 4 techniques:
//
// 1. We define 2 sectional constructs above the C++ language level -- the Block and the Sub-Block.
// Special demarcators inside comments are used to define blocks and sub-blocks.
// Blocks and sub-blocks serve the function of high-level codebase navigation.
//
// 2. We display the source-code in a 3 column format
// with a wider center column that is used to work on the code
// and 2 narrower side columns to see the preceding and following code sections.
// This makes is possible to view roughtly 120 lines of code at a time
// which covers even the longest functions.
//
// 3. We use kybd based code folding (which we call summarization).
// Any sectional element in the code can be summarized.
// Blocks and sub-blocks, functions and structures, enums and comment blocks can all be summarized.
// Any line that ends in a {...} is summarized and can be opened.
// Summarisation is very efficient and blends in well with code-editing and navigation.
// Since the developer is already using the kybd for these functions
// they do not have to switch over to the mouse at all.
//
// 4. We provide the ability to directly navigate to any symbol (struct, class or fn)
// by moving the caret to a symbol's name and pressing Ctrl-Right.
// If there is a symbol at the caret location,
// the current editor view-context is "collapsed",
// and the editor goes to and expands the symbol's definition.
// If there is no symbol at the caret location
// A text entry field is opened into which the name of a symbol can be typed in.
// A history of such goto's is maintained.
// Pressing Ctrl-Left returns from the goto by collapsing it
// and returning to and expanding the previous context.
// This is similar to the functionalty provided by other IDEs
// but it's kybd based and hence more efficient.
//
// NAVIGATIONAL CONTROLS
// This codefile currently contains only navigational mechanisms
// enabling developers new to the Modal UI toolkit to understand it.
// Later I will add editing, building and debugging features.
//
// 1. The arrow keys and PgUp, PgDn are used to move the caret
//
// 2. Summarization is done by moving the caret the first line of any sectional element
// and pressing Ctrl-S.
// Pressing Ctrl-S again (on the summarised line) expands the section.
// The way you navigate a Modal codefile using this app is
// you open a section to study its code,
// then you close it, open another section and study its code.
// You only keep open the section you're currently working on.
//
// 3. Ctrl-Right and Ctrl-left serves as a goto and back as described earlier.
//
// 4. Pressing and releasing Ctrl pop's up a small menu of selectable commands.
// Only the last of these, "adjust fontsize", is implemented.
// On selecting it using up-down arrow and pressing enter
// fontsize can be adjusted using the arrow keys.
//
// 5. Pressing Escape in any operational context exits that operational context.
// If that context is the primary mode, it exits the app.
// BLOCK: INCLUDES AND FWD_DECLS
#include "wx/wxprec.h"
#include "wx/wx.h"
#include "wx/textfile.h"
#include "wx/utils.h"
#include "wx/file.h"
#include "wx/font.h"
#include "wx/dcbuffer.h"
#include "wx/dir.h"
#include "wx/filename.h"
#include "wx/filefn.h"
#include "wx/stdpaths.h"
struct SModeMsg;
struct SModeFileSel;
struct SModeLineInp;
struct SModeIntDisp;
struct SModeSrcEdr;
struct SModeLevAdj;
struct SMode;
class MyFrame;
struct SModeManager;
class ModalWindow;
SMode* load_mode(int scrnW, int scrnH, wxFile& File);
void free_mode(SMode* pMode);
void mode_on_load(SMode* pMode, SModeManager *pModeManager);
void mode_on_unload(SMode* pMode, SModeManager* pModeManager);
bool mode_key_up(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
bool msg_map(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
void msg_disp_state(SMode* pMode, ModalWindow* pWin, wxDC& DC);
void msg_disp_update(SMode* pBase, int phase, ModalWindow* pWin, wxDC& DC);
bool line_input_map(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
bool line_input_key_up(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
void line_input_disp_state(SMode* pMode, ModalWindow* pWin, wxDC& DC);
void line_input_disp_update(SMode* pBase, int phase, ModalWindow* pWin, wxDC& DC);
bool lev_adj_map(SMode* pMode, wxKeyEvent& event, ModalWindow* pWin);
void lev_adj_disp_state(SMode* pMode, ModalWindow* pWin, wxDC& DC);
void lev_adj_adjust(SMode* pBase, int phase, ModalWindow* pWin, wxDC& DC);
bool file_sel_map(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
void file_sel_disp_state(SMode* pMode, ModalWindow* pWin, wxDC& DC);
void file_sel_change_sel(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void file_sel_commit(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
bool int_disp_map(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
void int_disp_disp_state(SMode* pMode, ModalWindow* pWin, wxDC& DC);
void int_disp_change_sel(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void int_disp_execute(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
bool src_edr_map(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
bool src_edr_key_up(SMode* pMode, wxKeyEvent& Event, ModalWindow* pWin);
void src_edr_disp_state(SMode* pMode, ModalWindow* pWin, wxDC& DC);
void src_edr_disp_update(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_on_load(SMode* pMode, SModeManager* pManager);
void src_edr_on_unload(SMode* pMode, SModeManager* pManager);
bool src_edr_serialize(SMode* pBase, wxFile& File, bool bToFrom);
void src_edr_edit_char(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_update_caret(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_start_sel(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_update_sel(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_un_sel(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_cutpaste_sel(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_undoredo(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_summarize(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_goto(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_control(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_scroll(SMode* pMode, bool bUp, bool pPageLine, ModalWindow* pWin);
void src_edr_export(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_load_new(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_build(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_debug(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_adjust_fontsize(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
void src_edr_input_codefile(SMode* pMode, int phase, ModalWindow* pWin, wxDC& DC);
SModeManager* modal_init(int scrnWidth, int scrnHeight);
void modal_exit(SModeManager* pModeManager);
#define ABS(x) ((x)>0?(x):-(x))
// BLOCK: WX INTERFACING BOILERPLATE
// Modal's SModeManager interfaces with the wxWindow class of wxWidgets.
// The ModeManager is concerned with receiving kybd events
// sending screen redraw (refresh) requests
// and receiving notifications (events) for redrawing the screen.
// It is also concerned with being initialized on app init
// and being called to exit when the user exits the app
// ModalWindow, a subclass of wxWindow provides all the interfacing needed.
// It calls modal_init() in its constructor
// which returns a loaded mode manager,
// and calls modal_exit() in its destructor
// which serialzes the mode manager to disk and then free's the ModeManager
// It also dispatches kybd and paint events to the mode manager
// and provides access to wxWindow::Refresh methods to send redraw requests
// SUBBLOCK: WX BRIDGE STRUCTURES AND FUNCTIONS
// this ifdef is for debugging heap corruption errors on Windows
#ifdef __WXMSW__
#define _CRTDBG_MAP_ALLOC
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <crtdbg.h>
#endif
// The Modal Window is a subclass of wxWindow that interfaces with the ModeManager
// It contains a ModeManager member struct
// this struct get's initialized in ModalWindow's constructor
// via a call to modal_init()
// and serialized to disk and freed in ModalWindow's destructor via modal_exit()
// ModalWindow passes key events to the ModeManager
// and also wxPaint events
// modes (contained in the mode manager) also use wxWindow::Refresh()
// (via ModalWindow) to refresh parts or all of the screen
class ModalWindow : public wxWindow {
public:
ModalWindow( MyFrame *pOwner, wxSize Size );
virtual ~ModalWindow();
void EraseBG(wxEraseEvent& Event); // this is needed by wxWidgets
void OnPaint(wxPaintEvent &Event);
void OnKeyDown(wxKeyEvent &Event);
void OnKeyUp(wxKeyEvent &Event);
void OnLostFocus(wxFocusEvent &Event); // this is needed for a special case
MyFrame *m_pOwner;
SModeManager *m_pModeManager;
bool m_bUsrActn; // for OnPaint. Did the paint event come from a user action or from the OS
wxDECLARE_EVENT_TABLE();
};
// an event table to map the events for ModalWindow : wxWindow
wxBEGIN_EVENT_TABLE(ModalWindow, wxWindow)
EVT_PAINT(ModalWindow::OnPaint)
EVT_ERASE_BACKGROUND(ModalWindow::EraseBG)
EVT_KEY_DOWN(ModalWindow::OnKeyDown)
EVT_KEY_UP(ModalWindow::OnKeyUp)
EVT_KILL_FOCUS(ModalWindow::OnLostFocus)
wxEND_EVENT_TABLE()
// SUBBLOCK: MODAL'S BASE STRUCTURES -- MODE-MANAGER AND MODE
// The maximum number of "user intents" any given mode can accomodate
#define MAX_INTENTS 40
// Mode types
enum {
MODE_BASE=0,
// these are accessory modes provided by the toolkit
MODE_INTENT_DISPATCHER,
MODE_MESSAGE,
MODE_LINE_INPUT,
MODE_FILE_SELECTOR,
MODE_LEVEL_ADJUSTER,
// app specific primary mode IDs need to be enumed here
MODE_SOURCE_EDITOR
};
// union for extending the base mode structure
// it is contained by the base Mode structure
// If you define a new mode for your app,
// it's mode extension needs to be added here
// for example, this app added SModeSrcEdr
// You also need to add code to free this mode extension
// in the implementation of free_mode()
// and an element to the mode types enum
typedef union UModeExtension {
// these are accessory modes provided by the toolkit
SModeIntDisp *pIntDisp;
SModeMsg * pMsg;
SModeLineInp * pLineInput;
SModeFileSel *pFileSel;
SModeLevAdj * pLevAdj;
// app specific primary modes are added here
SModeSrcEdr * pSrcEdr;
} UModeExtension;
// A mode (of operation) is represented by the SMode struct
// SMode is a struct that is like an abstract class.
// It contains several fn ptrs that have to be loaded by a concrete mode.
// and it contains a ptr to a union UModeExtension
// A concrete mode is defnied by this extension
// and by the specific implementation fns it loads into its base mode.
// SMode's loadable fn ptrs are for all the functions need in its primary operation
// such as responding to kybd and display events
// load or unload events when it is loaded or unloaded from the mode manager
// and a serialization fn for when its state is stored/loaded to/from disk
//
// USER INTENTS: HOW A MODE IS DESIGNED
// A mode is designed as a set of "user intents".
// The mode designer determines what user keystroke combinations
// will correspond to what app function.
// Each one of these is called a User Intent.
// The designer defines these intents
// and creates intent handlers that will perform the function associated with that intent.
// SMode contains an array of intent_handler fn ptrs.
// By convention, a mode extension loads these in a load_intents() fn
// that is called by its init() fn
// A mode extension, in it's kybd_map() processor
// determines and sets current user intent (an index)
// and dispatches processing to the mode's intent handler array.
// That intent handler does the processing needed to handle that user intent
// then either refresh's a part of or all of the screen using wxWindow::Refresh().
// This generates a wxPaint event that get's send down by ModalWindow to the mode manager.
// The mode manager determines the currently operating mode and intent
// and dispatches the display processing to that mode intent_handler fn
// The intent handler therefore has 2 phases of operation --
// the first when the mode's kybd_map() fn calls it (phase PH_NOTIFY)
// and the second when the modemanager's paint evt handler calls it (phase PH_EXEC).
//
// THE MODE'S DISPLAY STATE: INITIAL DRAWING
// Every mode must maintain a display state
// which is displayed when the mode is first loaded by the modemanager
// or in response to an OS event such as switching out and in of the app.
// Whenever an intent handler draws directly to the display,
// it must update this display state to reflect the changes it has made.
// The intent handler may also choose to only make changes to the display state of its mode
// and not do any drawing to the screen of its own
// then set ModalWindow::m_bUsrActn to false and issue a full-screen refresh
// which will call fnDispState.
// This is less efficient than updating only a part of the screen
// but simpler to implement.
typedef struct SMode {
void init( int scrnW, int scrnH, wxFont *pFont ) {
// the base mode struct loads these 3 default fns that define default behavior
this->fnKey_up = mode_key_up;
this->fnOn_load = mode_on_load;
this->fnOn_unload = mode_on_unload;
this->scrnW = scrnW;
this->scrnH = scrnH;
this->bHasFocus = false;
this->bShiftDown = false;
this->bCtrlDown = false;
this->pFont = pFont;
this->dFontScale = 1.0;
if (pFont != NULL)
this->set_font(pFont);
else
this->dBaseFontPointSize = 0.0;
this->bReset = true;
this->type = MODE_BASE;
};
// all of the fn ptrs below have to be assigned for a mode to be operational
bool (*fnKey_up)( SMode *pMode, wxKeyEvent &Event, ModalWindow *pWin );
bool (*fnKybd_map)( SMode *pMode, wxKeyEvent &Event, ModalWindow *pWin );
void (*fnDisp_state)( SMode *pMode, ModalWindow *pWin, wxDC &DC );
void (*fnOn_load)( SMode *pBase, SModeManager *pManager );
void (*fnOn_unload)( SMode *pBase, SModeManager* pManager);
bool (*fnSerialize)( SMode *pBase, wxFile &File, bool bToFrom );
void (*fnIntent_handler[MAX_INTENTS])( SMode *pBase, int phase, ModalWindow *pWin, wxDC &DC );
// updates the display for this mode in response to a user action
// called by the mode manager
void disp_update( int phase, ModalWindow * pWin, wxDC &DC ) {
// dispatch the current user intent for display
this->fnIntent_handler[this->intent]( this, phase, pWin, DC );
};
// set's the font but does not load it yet
void set_font( wxFont *pFont ) {
this->pFont = pFont;
this->dBaseFontPointSize = pFont->GetFractionalPointSize();
};
// load's the currently set font with it's scale
// this fn must be called before using the font to do something
// so that the font is loaded at it's intended scale
void load_font() {
if( this->pFont != NULL )
if( this->dFontScale > 1.0 )
this->pFont->SetFractionalPointSize(this->dBaseFontPointSize * this->dFontScale);
};
void adjust_font_scale(double dFontScaleAdjust) {
this->dFontScale = (this->dFontScale * dFontScaleAdjust);
this->load_font();
}
// sets the location of this mode if it is a pop-up
// the location is relative to the center of the screen
// and is in pixel coordinates.
void set_location( wxPoint Location ) {
this->Location = Location;
};
// serializes the base data of a mode
void serialize( wxFile &File, bool bToFrom ) {
if( bToFrom ) {
File.Write( &(this->numIntents), sizeof(int) );
File.Write( &(this->type), sizeof(int) );
// serialize the FontScale
File.Write( &(this->dFontScale), sizeof(double) );
}
else {
File.Read( &(this->numIntents), sizeof(int) );
File.Read( &(this->type), sizeof(int) );
// serialize the font scale
File.Read( &(this->dFontScale), sizeof(double) );
}
}
int numIntents;
int intent;
int intentPrev;
wxPoint Location;
UModeExtension sExt;
int scrnW;
int scrnH;
int type;
bool bHasFocus;
int key;
int uniKey;
bool bShiftDown;
bool bCtrlDown;
wxFont *pFont;
double dFontScale;
double dBaseFontPointSize;
bool bReset;
} SMode;
// phases of an intent implementation function
// the kybd map function initiates intent dispatch
// by calling an intent function in PH_NOTIFY phase
// the intent function either generates its own refresh
// in which case, when the paint event is called,
// the paint event handler completes intent dispatch
// by calling the intent function with PH_EXEC
// in which case the intent function draws to the screen the rects it refreshed.
// The intent function during PH_NOTIFY may also generate a global refresh
// by setting g_UsrActn false and calling ModalWindow : wxWindow::Refresh
// in this case paint event processing calls the mode manager
// which calls the disp_state method of the mode
// causing a full redraw
// using this technique, we can localize wxWindow::RefreshRect and it's associated drawing
// to a single function
enum {
PH_NOTIFY, PH_EXEC
};
// a mode link structure used to implement a stack of modes (used by the ModeManager)
typedef struct SModeLink {
SMode *pMode;
SModeLink *pNextLink;
} SModeLink;
// the ModeManager that interfaces with wxWidgets
// and manages the modes (of operation) of a Modal UI
// modes are pushed onto and popped off the mode manager
// the mode manager manages the dispatch of kybd and paint events
// that are sent down to it from the ModalWindow.
// Kybd events are sent to the mode at the top of the stack.
// Paint events if they are full-screen refresh's
// cause the back-to-front state display of each mode in the stack
// if they are caused by a user action, they are sent
// to the display update method of the mode at the top of the stack.
typedef struct SModeManager {
SModeLink Stack;
SMode *pCurMode; // the mode currently at the top of the stack
int scrnW;
int scrnH;
wxFont* pFont;
// inits with the screen dimensions
void init( int scrnW, int scrnH, wxFont *pFont ) {
Stack.pMode = NULL;
Stack.pNextLink = NULL;
this->scrnW = scrnW;
this->scrnH = scrnH;
this->pCurMode = NULL;
this->pFont = pFont;
};
void push( SMode *pMode ) {
pMode->set_font(this->pFont);
bool bLast = false;
SModeLink *pThisLink = &(this->Stack);
// uninitialized stack, add the mode to the root of the stack
if( pThisLink->pMode == NULL ) {
pThisLink->pMode = pMode;
pThisLink->pNextLink = NULL;
this->pCurMode = pMode;
this->pCurMode->fnOn_load( this->pCurMode, this );
this->pCurMode->bHasFocus = true;
}
// stack has been inited. Find the last link and add a new link
else {
// Find the last link and add a new link
while( !bLast ) {
// this is the last link, add a new link and exit the find
if( pThisLink->pNextLink == NULL ) {
bLast = true;
pThisLink->pNextLink = (SModeLink *) malloc( sizeof( SModeLink ) );
pThisLink->pNextLink->pMode = pMode;
pThisLink->pNextLink->pNextLink = NULL;
this->pCurMode = pMode;
this->pCurMode->fnOn_load(this->pCurMode, this);
this->pCurMode->bHasFocus = true;
}
else
pThisLink = pThisLink->pNextLink;
}
}
};
// pop a mode from the mode manager
// returns false if the stack is now at init state
bool pop() {
bool bRetVal = true;
bool bLast = false;
SModeLink *pThisLink = &(this->Stack);
// the first link is the last link so return false
if( pThisLink->pNextLink == NULL )
bRetVal = false;
// find the last link and pop it, call unload on the mode that got popped off
else {
// find the last link and pop it, call unload on the mode that got popped off
while( !bLast ) {
// this is the last link, pop it off and exit the find
if( pThisLink->pNextLink->pNextLink == NULL ) {
bLast = true;
pThisLink->pNextLink->pMode->bHasFocus = false;
pThisLink->pNextLink->pMode->fnOn_unload( pThisLink->pNextLink->pMode, this );
free( pThisLink->pNextLink );
pThisLink->pNextLink = NULL;
this->pCurMode = pThisLink->pMode;
this->pCurMode->bHasFocus = true;
}
else
pThisLink = pThisLink->pNextLink;
}
}
return( bRetVal );
};
// replace the node at the top of mode stack
void replace( SMode *pNewMode ) {
pNewMode->set_font(this->pFont);
bool bLast = false;
SModeLink *pThisLink = &(this->Stack);
// find the last link in the stack and replace it
while( !bLast ) {
// this is the last link, replace it and exit the find
if( pThisLink->pNextLink == NULL ) {
bLast = true;
pThisLink->pMode->fnOn_unload( pThisLink->pMode, this );
pThisLink->pMode = pNewMode;
this->pCurMode = pNewMode;
this->pCurMode->fnOn_load( this->pCurMode, this );
this->pCurMode->bHasFocus = true;
}
else
pThisLink = pThisLink->pNextLink;
}
return;
};
// displays the current state of the mode stack
// this method is called by ModalWindow : wxWindow :: OnPaint
// when it receives a paint event that was not caused by a user action
// which implies the entire screen is to be repainted.
// it calls disp_state on each of the modes in the stack
// starting from the primary mode
void disp_state( ModalWindow *pWin, wxDC& DC ) {
// draw a BG color (208,208,200) rect over the entire screen
wxRect rect;
rect.x = 0;
rect.y = 0;
rect.width = this->scrnW;
rect.height = this->scrnH;
wxPen Pen = DC.GetPen();
DC.SetPen( *wxTRANSPARENT_PEN );
DC.SetBrush( wxColour( 200,200,200 ) );
DC.DrawRectangle( rect );
DC.SetPen( Pen );
bool bLast = false;
SModeLink *pThisLink = &(this->Stack);
// draw each link in the stack from bottom to top (back to front)
if( pThisLink->pMode != NULL ) {
// draw each link till the last one is reached
while( !bLast ) {
// this is the last link, draw it and exit
if( pThisLink->pNextLink == NULL ) {
bLast = true;
pThisLink->pMode->fnDisp_state( pThisLink->pMode, pWin, DC );
}
// this is not the last link, draw it and load nextlnk
else {
pThisLink->pMode->fnDisp_state( pThisLink->pMode, pWin, DC );
pThisLink = pThisLink->pNextLink;
}
}
}
return;
};
// updates the display by calling the currently active mode's disp_update
// called by the ModalWindow's OnPaint when the paint evt is in the context of a user action
void disp_update( ModalWindow *pWin, wxDC& DC ) {
// display update only the mode at the top of the stack
if (this->pCurMode != NULL) {
this->pCurMode->disp_update( PH_EXEC, pWin, DC );
pWin->m_bUsrActn = false;
}
return;
};
// calls the fnKybd_map() fn of the currently active mode
bool kybd_map( wxKeyEvent &Event, ModalWindow *pWin ) {
pWin->m_bUsrActn = true;
if (this->pCurMode != NULL)
this->pCurMode->fnKybd_map(this->pCurMode, Event, pWin);
return(true);
}
// calls the fnKey_up() fn of the currently active mode
bool key_up( wxKeyEvent &Event, ModalWindow *pWin ) {
pWin->m_bUsrActn = true;
if (this->pCurMode != NULL)
this->pCurMode->fnKey_up(this->pCurMode, Event, pWin);
return(true);
}
// resets the stored kybd state for all contained modes.
// this fn is called when a paint evt occurs due to a system action
// such as a window swap out swap in.
// Why is this needed?
// When a kybd event is processed by a mode kybd-map fn,
// it is not processed within the kybd_map fn
// instead, it's intent is determined
// and the processing is dispatched to the intent handler
// this means the kybd state info has to be conveyed to the intent handler.
// This is done by storing this state in the SMode struct.
// If the app is switched out and switched back in
// the values of these kybd state params become invalid
// in particular bCtrlDown and bShiftDown
// These need to be reset before further kybd event processing.
// Hence this fn is called by ModalWindow::OnLostFocus
// which is called when the window gets switched out
void reset_kybd_state() {
SModeLink* pThisLink = &(this->Stack);
bool bLast = false;
// reset kybd state on each next link (if !NULL) till last link starting from first
if (pThisLink->pMode != NULL) {
// reset kybd state on each next link till last link
while (!bLast) {
// reset kybd state
pThisLink->pMode->bCtrlDown = false;
pThisLink->pMode->bShiftDown = false;
if (pThisLink->pNextLink == NULL)
bLast = true;
else
pThisLink = pThisLink->pNextLink;
}
}
}
// serializes to/from a file the state of this mode manager
// called when a Modal app exits
// stores the mode stack and every mode in the mode stack
bool serialize( wxFile &File, bool bToFrom ) {
bool bRetVal = true;
bool bNextLink = false;
// store each next link (if !NULL) starting from the first
if( bToFrom ) {
SModeLink *pThisLink = &(this->Stack);
bool bLast = false;
// the stack is not empty, traverse throguh its links and serialize them
if( pThisLink->pMode != NULL ) {
// keep traversing till end of stack or link serialization error
while( !bLast && bRetVal ) {
// this is the last link, serialize it and end the process
if( pThisLink->pNextLink == NULL ) {
bLast = true;
// write out a 0 for next link
bNextLink = false;
File.Write( &bNextLink, sizeof(bool) );
// store the state of this link's mode
pThisLink->pMode->serialize( File, true );
pThisLink->pMode->set_font(this->pFont);
bRetVal = pThisLink->pMode->fnSerialize( pThisLink->pMode, File, true );
}
// there are more links to traverse, serialize this link and goto next link
else {
// write out a 1 for next link
bNextLink = true;
File.Write( &bNextLink, sizeof(bool) );
pThisLink->pMode->serialize( File, true );
bRetVal = pThisLink->pMode->fnSerialize( pThisLink->pMode, File, true );
pThisLink = pThisLink->pNextLink;
}
}
}
}
// load the stack from the serialized data
else {
bool bNextLink = true;
SModeLink *pNextLink = &(this->Stack);
// keep loading links till the last link is reached
while (bNextLink) {
File.Read(&(bNextLink), sizeof(bool));
pNextLink->pMode = load_mode(scrnW, scrnH, File);
pNextLink->pMode->set_font(this->pFont);
// there are more links. Load this link and goto next link
if (bNextLink) {
pNextLink->pNextLink = (SModeLink*)malloc(sizeof(SModeLink));
pNextLink = pNextLink->pNextLink;
}
// there are no more links. Signal link load exit. Set curMode
else {
pNextLink->pNextLink = NULL;
this->pCurMode = pNextLink->pMode;
}
}
}
return( bRetVal );
};
} SModeManager;
// allocs inits a mode manager ptr on the heap and returns it.
// The mode manager is inited with an initial mode, the width and height of the screen
SModeManager * new_mode_manager( int scrnW, int scrnH, wxFont *pFont ) {
SModeManager *pModeManager = (SModeManager *) malloc( sizeof(SModeManager) );
pModeManager->init( scrnW, scrnH, pFont );
return( pModeManager );
}
// loads a mode manager from state stored in a file
SModeManager *load_mode_manager( int scrnW, int scrnH, wxFont *pFont, wxFile &File ) {
SModeManager *pModeManager = (SModeManager *) malloc( sizeof(SModeManager) );
pModeManager->init( scrnW, scrnH, pFont );
pModeManager->serialize( File, false );
return( pModeManager );
}
// frees a mode manager
void free_mode_manager( SModeManager *pModeManager ) {
// free each modelink (and its associated mode) in the mode stack
// starting from the last link
// to do this, we make a linear list of links and free it in reverse order
int numLinks = 0;
SModeLink* pThisLink = &(pModeManager->Stack);
// determine the number of links
while( pThisLink != NULL ) {
pThisLink = pThisLink->pNextLink;
numLinks++;
}
SModeLink** ppLinkSet = (SModeLink **)malloc(numLinks * sizeof(SModeLink *));
int index = 0;
ppLinkSet[index] = &(pModeManager->Stack);
pThisLink = &(pModeManager->Stack);
// load the linked list after the first link into the linkset
while (pThisLink != NULL) {
pThisLink = pThisLink->pNextLink;
if (pThisLink != NULL) {
index++;
ppLinkSet[index] = pThisLink;
}
}
// free the link set
for (int i = numLinks - 1; i >= 0; i--) {
free_mode( ppLinkSet[i]->pMode );
// dont free the first link since that's on the stack and not the heap
if( i>0 )
free(ppLinkSet[i]);
}
free( ppLinkSet );
delete pModeManager->pFont;
free( pModeManager );
}
// SUBBLOCK: WX APP & CLASS FUNCTION DEFINITIONS
// This sub-block contains the definitions of the app and wx classes
// and their interfaces to the Modal UI toolkit's bridge functions
class MyFrame : public wxFrame {
public:
MyFrame(const wxString& strTitle, const wxPoint& Pos, const wxSize& Size);
private:
ModalWindow *m_pWin;
};
// the app class
class MyApp : public wxApp {
public:
virtual bool OnInit() wxOVERRIDE;
};
// macro to implement the entry point for the app class
// in a cross-platform way
wxIMPLEMENT_APP(MyApp);
// the app's entry point
bool MyApp::OnInit() {
if( !wxApp::OnInit() )
return false;
int scrnWidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X,NULL);
int scrnHeight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y,NULL)-20;
// we offset the y location by 20 so the OS's menu is visible at the top above this app's window
MyFrame *pFrame = new MyFrame("Modal UI Toolkit for wxWidgets", wxPoint(0,20), wxSize(scrnWidth, scrnHeight));
pFrame->Show(true);
return true;
}
MyFrame::MyFrame(const wxString& strTitle, const wxPoint& Pos, const wxSize& Size) :
wxFrame((wxFrame *)NULL, wxID_ANY, strTitle, Pos, Size) {
wxSize SizeCanvas = Size;
// we deduct 25 from the height to account for the title bar of the frame
SizeCanvas.SetHeight(Size.GetHeight()-25);
m_pWin = new ModalWindow( this, SizeCanvas );
return;
}
ModalWindow::ModalWindow(MyFrame *pOwner, wxSize Size ) : wxWindow(pOwner, wxID_ANY, wxPoint( 0, 0 ), Size ) {
// this is needed by wxWidgets for implementing wxAutoBufferedDC
SetBackgroundStyle(wxBG_STYLE_PAINT);
// this is needed for then the entire screen is being scaled for a HiDPI display
double dScale = GetContentScaleFactor();
m_pModeManager = modal_init( Size.GetWidth()/dScale, Size.GetHeight()/dScale );
m_bUsrActn = false;
m_pOwner = pOwner;
}
ModalWindow::~ModalWindow() {
modal_exit(m_pModeManager);
}
void ModalWindow::OnPaint(wxPaintEvent& event) {
wxAutoBufferedPaintDC DC(this);
// event was produced by the OS (load or relaod app) not the user
if (!m_bUsrActn)
m_pModeManager->disp_state(this, DC);
// event is a response to a user action
else
m_pModeManager->disp_update(this, DC);
return;
}
// Needed by wxWidgets, helps to reduce flicker
void ModalWindow::EraseBG(wxEraseEvent& event) {
return;
}
void ModalWindow::OnKeyDown(wxKeyEvent& event) {
if (m_pModeManager != NULL)
m_pModeManager->kybd_map( event, this );
return;
}
void ModalWindow::OnKeyUp(wxKeyEvent& event) {
if (m_pModeManager != NULL)
m_pModeManager->key_up( event, this );
return;
}
// Processes the event when window loses focus
// resets the kybd state of the mode manager
// Why is this needed?
// When a kybd event is processed by a mode kybd-map fn,
// it is not processed within the kybd_map fn
// instead, it's intent is determined
// and the processing is dispatched to the intent handler
// this means the kybd state info has to be conveyed to the intent handler.
// This is done by storing this state in the SMode struct.
// If the app is switched out and switched back in
// the values of these kybd state params become invalid
// in particular bCtrlDown and bShiftDown
// These need to be reset before further kybd event processing.
// Hence reset_kybd_state is called here
void ModalWindow::OnLostFocus(wxFocusEvent& event) {
if (m_pModeManager != NULL)
m_pModeManager->reset_kybd_state();
event.Skip();
return;
}
// BLOCK: UTILITIES PROVIDED BY THE TOOLKIT
// Some utlity structs and fns provided by Modal
// Text processing for line and pages of text
// Kybd event handling helpers
// SUBBLOCK: TEXT PROCESSING UTILITIES
// This sub-block contains text processing related utilities
// 2 structures and associated function are provided
// STxtLine for representing and processing a text line
// and STxtPage for representing and processing a page of TxtLines
// e.g. They are used by the source editor mode implementation
// struct for representing a line of txt represented as chars (not unicode)
// and several functions to manipulate a line of text
typedef struct STxtLine {
char *szBuf;
int length;
int maxLength;
} STxtLine;
// gets the screen location of the caret at the specified index in the line
// based on the current font loaded in the DC
int tl_caret_loc(STxtLine* pLine, int index, wxDC& DC, ModalWindow *pWin ) {
int retVal = 0;
int strLen;
wxASSERT_MSG(index >= 0, "index OOR in tl_caret_loc");
if (index > pLine->length)
index = pLine->length;
strLen = index;
char* szTemp = (char*) malloc( strLen+1 * sizeof(char));
for( int i=0; i<strLen; i++ )
szTemp[i] = pLine->szBuf[i];
szTemp[strLen] = 0;
int width;
int height;
DC.GetTextExtent( wxString( szTemp ), &width, &height );
retVal = width - 1;
free(szTemp);
return( retVal );
}
// creates a new txt_line struct ptr on the heap using the specified zero terminated string
// maxLength is how large this line can get
STxtLine* new_txt_line( char *szData ) {
STxtLine* pRetVal = (STxtLine*)malloc(sizeof(STxtLine));
wxASSERT_MSG(pRetVal != NULL, "malloc failure");
int len = 0;
// create a line with 100 chars if pcData == NULL else strlen(pcData) chars
// create a line with 100 chars
if( szData == NULL ) {
len = 50;
pRetVal->maxLength = len + 1;
pRetVal->length = len;
pRetVal->szBuf = (char*)malloc((pRetVal->maxLength + 1) * sizeof(char));
}
// create a line with strlen( szData )
else {
len = strlen(szData);
pRetVal->maxLength = len * 2 + 1;
pRetVal->length = len;
pRetVal->szBuf = (char*)malloc((pRetVal->maxLength + 1) * sizeof(char));
if (pRetVal->szBuf != NULL) {
for (int i = 0; i < pRetVal->length; i++)
pRetVal->szBuf[i] = szData[i];
pRetVal->szBuf[pRetVal->length] = 0;
}
}
return( pRetVal );
};
// frees a txt line
void tl_free( STxtLine *pLine ) {
if (pLine != NULL) {
if (pLine->szBuf != NULL) {
free(pLine->szBuf);
pLine->szBuf = NULL;
}
free(pLine);
}
};
// creates a new txt_line struct ptr on the heap using the specified wxString
// maxLength is how large this line can get
STxtLine * new_txt_line_wx( wxString strFrom ) {
STxtLine * pRetVal = (STxtLine *) malloc( sizeof(STxtLine) );
wxASSERT_MSG(pRetVal != NULL, "malloc failure");
const char *szTemp = static_cast<const char *>(strFrom.c_str());
int len = strlen( szTemp );
pRetVal->maxLength = len * 2 + 1;
pRetVal->length = len;
pRetVal->szBuf = (char *) malloc( (pRetVal->maxLength+1) * sizeof(char) );
wxASSERT_MSG(pRetVal->szBuf != NULL, "malloc failure");
for (int i = 0; i < pRetVal->length; i++)
pRetVal->szBuf[i] = szTemp[i];
pRetVal->szBuf[pRetVal->length] = 0;
return( pRetVal );
}
// creates a txt_line on the stack using the specified szString
// maxLength is set to #define MAX_TXT_LINE_LENGTH
STxtLine get_txt_line( char pcFrom[] ) {
int length = strlen( pcFrom );
STxtLine RetVal;
RetVal.maxLength = length * 2 + 1;
RetVal.length = length;
RetVal.szBuf = (char *) malloc( (RetVal.maxLength+1) * sizeof(char) );
wxASSERT_MSG(RetVal.szBuf != NULL, "malloc failure");
for (int i = 0; i < RetVal.length; i++)
RetVal.szBuf[i] = pcFrom[i];
RetVal.szBuf[RetVal.length] = 0;
return( RetVal );
}
// creates a new txt_line ptr on the heap by cloning the specified STxtLine
// caller must free this ptr
STxtLine * tl_clone( STxtLine *pszFrom ) {
STxtLine *pRetVal = (STxtLine *) malloc( sizeof(STxtLine) );
wxASSERT_MSG(pRetVal != NULL, "malloc failure");
pRetVal->maxLength = pszFrom->maxLength;
pRetVal->length = pszFrom->length;
pRetVal->szBuf = (char *) malloc((pRetVal->maxLength + 1) * sizeof(char));
wxASSERT_MSG(pRetVal->szBuf != NULL, "malloc failure");
for (int i = 0; i < pRetVal->length; i++)
pRetVal->szBuf[i] = pszFrom->szBuf[i];
pRetVal->szBuf[pRetVal->length] = 0;
return( pRetVal );
}
// inserts the sepcified char at the specified location in the txtline
// if index is -1 or greater than txtline->length, char is appended to the end
void tl_insert_char( STxtLine *pThis, char cChar, int index ) {
int idx;
wxASSERT_MSG( index >=0 && index <= pThis->length, "index OOR in tl_insert_char");
idx = index;
if( idx >= pThis->maxLength ) {
pThis->maxLength = pThis->maxLength * 2 + 1;
pThis->szBuf = (char *) realloc( pThis->szBuf, pThis->maxLength * sizeof(char) );
}
for( int i=pThis->length-1; i>=idx; i-- )
pThis->szBuf[i+1] = pThis->szBuf[i];
pThis->szBuf[idx] = cChar;
pThis->length += 1;
return;
}
// deletes char at the specified location in the txtline
// if index is -1 or greater than txtline->length, char is deleted from the end
void tl_delete_char( STxtLine *pThis, int index ) {
int idx;
wxASSERT_MSG(index >= 0 && index <= pThis->length, "index OOR in tl_insert_char");
idx = index;
for( int i=idx; i<(pThis->length); i++ )
pThis->szBuf[i] = pThis->szBuf[i+1];
if( pThis->length > 0 )
pThis->length -= 1;
return;
}
// cut's out the substring between from and to in pLine
// and returns the cutout as an szString
// caller must free
char * tl_cut_out( STxtLine *pThis, int from, int to ) {
char * pcRetVal = NULL;
wxASSERT_MSG(!(from < 0 || to > pThis->length || to < from), "invalid indices in call to tl_cut_out");
pcRetVal = (char *) malloc( (to-from+1) * sizeof( char ) );
wxASSERT_MSG( pcRetVal != NULL, "malloc failure");
for (int i = from; i < to; i++)
pcRetVal[i - from] = pThis->szBuf[i];
pcRetVal[to - from] = 0;
for (int i = from; i < from + pThis->length - to + 1; i++)
pThis->szBuf[i] = pThis->szBuf[i + to - from];
pThis->length -= (to - from);
return( pcRetVal );
}
// inserts the specified szString at the specified location in TxtLine
void tl_insert( STxtLine *pThis, char szToken[], int at ) {
int length = strlen( szToken );
if( (pThis->length + length + 1) > pThis->maxLength ) {
pThis->maxLength = pThis->length + length + 1;
pThis->szBuf = (char *) realloc( pThis->szBuf, pThis->maxLength * sizeof(char) );