c_text.cpp 147 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257
  1. // c_text.cpp
  2. // Support for a window that can support regular, bold, italic and symbol
  3. // fonts each in large & small, plus the ability to select the size of
  4. // characters to be used.
  5. //
  6. //
  7. // See accompanying file "cwin.tex" for an explanation of the policy
  8. // for scrolling, text selection and treatment of buffer over-full
  9. // conditions.
  10. //
  11. // Copyright (C)/©/¸ Codemist Ltd, 1995-99
  12. /* Signature: 7f4001bb 07-Mar-2000 */
  13. #include "cwin.hpp"
  14. // There are times when this code wants to indicate to the user's
  15. // application that it should terminate. If such a request has been
  16. // raised the macro exception_pending() given here will evaluate to true,
  17. // but the way that this happens is specific the the CSL/CCL Lisp system
  18. // and thus is not exactly generic.
  19. extern "C" {
  20. extern int C_nil;
  21. extern FILE *spool_file;
  22. extern void inject_randomness(int);
  23. }
  24. #define exception_pending() ((C_nil&1) != 0)
  25. static BOOL hasFinished = FALSE;
  26. CMainWindow::CMainWindow()
  27. {
  28. hasFinished = FALSE;
  29. complete = FALSE;
  30. char *menuName = "MainMenu";
  31. HINSTANCE hInst = AfxFindResourceHandle(menuName, RT_MENU);
  32. HMENU hMenu = ::LoadMenu(hInst, menuName);
  33. this->CreateEx(WS_EX_ACCEPTFILES,
  34. mainWindowClass, "",
  35. WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
  36. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  37. NULL, hMenu);
  38. // Now I will add the dynamic menu items that I need...
  39. // I demand that the "help" be the rightmost one on the menu bar
  40. HMENU helpMenu = ::GetSubMenu(hMenu, ::GetMenuItemCount(hMenu)-1);
  41. int i;
  42. theApp.dynamicCount = 0;
  43. // Insert extra help menu items here. I expect that the registry will
  44. // contain a subkey called HelpItems which is an integer, and then
  45. // a bunch of strings takked as T000/P000, T001/P001 etc where the Tiii are
  46. // strings to place on the menu and the Piii are paths to relevant help
  47. // files that can be opened.
  48. int helpCount = theApp.GetProfileInt("HelpItems", "HowMany", 0);
  49. #ifdef MAYBE
  50. int helpCount = theApp.GetProfileInt("HelpItems", "HowMany", -1);
  51. if (helpCount < 0)
  52. { theApp.dynamic[0] = "&Contents"
  53. theApp.dynamic_files[0] = "???";
  54. theApp.dynamic[1] = "&Search for Help"
  55. theApp.dynamic_files[1] = "???";
  56. helpCount = 2;
  57. WriteProfileInt("HelpItems", "HowMany", 2);
  58. WriteProfileString("HelpItems", "T000", theApp.dynamic[0]);
  59. WriteProfileString("HelpItems", "P000", theApp.dynamic_files[0]);
  60. WriteProfileString("HelpItems", "T001", theApp.dynamic[1]);
  61. WriteProfileString("HelpItems", "P001", theApp.dynamic_files[1]);
  62. }
  63. else
  64. #endif
  65. { if (helpCount >= IDM_LAST_DYNAMIC-IDM_DYNAMIC_ITEMS)
  66. helpCount = IDM_LAST_DYNAMIC-IDM_DYNAMIC_ITEMS-1;
  67. for (i=0; i<helpCount; i++)
  68. { char tag[8];
  69. sprintf(tag, "T%.3d", i);
  70. CString key(theApp.GetProfileString("HelpItems", tag, ""));
  71. sprintf(tag, "P%.3d", i);
  72. CString path(theApp.GetProfileString("HelpItems", tag, ""));
  73. if (key.GetLength() != 0 && path.GetLength() != 0)
  74. { char *m;
  75. m = (char *)malloc(1+key.GetLength());
  76. strcpy(m, LPCTSTR(key));
  77. theApp.dynamic[theApp.dynamicCount] = m;
  78. m = (char *)malloc(1+path.GetLength());
  79. strcpy(m, LPCTSTR(path));
  80. theApp.dynamic_files[theApp.dynamicCount++] = m;
  81. }
  82. }
  83. }
  84. // Now place them on the menu
  85. for (i=0; i<theApp.dynamicCount; i++)
  86. ::InsertMenu(helpMenu, IDM_HELP_ON_HELP, MF_BYCOMMAND,
  87. IDM_DYNAMIC_ITEMS+i, theApp.dynamic[i]);
  88. // typeAheadBuffer is a smallish buffer and when the user presses a key the
  89. // resulting character is put there. No echoing is performed. Use of DEL
  90. // can discard a previously-typed character and an attempt to over-fill the
  91. // buffer before other parts of the code have emptied it will cause a BEEP.
  92. //
  93. typeAheadP1 = typeAheadP2 = 0; // pointers into typeAheadBuffer
  94. // inputBuffer holds stuff that the user has accepted (by typing a newline)
  95. // but that the program has not yet read. inputP shows where the next
  96. // character should be read from it. The end of the buffer is marked with
  97. // a newline. If inputP<0 the buffer is empty.
  98. inputP = -1;
  99. // textBuffer is a large buffer just containing characters. textFirst
  100. // points to the first active character and textLast to one after the last
  101. // active character. Both pointers are kept masked with TEXT_MASK which
  102. // wraps them around to give a circular buffer.
  103. textFirst = textLast = 0; // main text buffer, now empty
  104. caretChar = 0; // char before which caret is shown
  105. caretLine = 0; caretX = 0;
  106. icaretChar = icaretLine = icaretX = 0;
  107. caretVisible = TRUE;
  108. caretFontWidths = windowFonts.HCourier.across;
  109. icaretFontWidths = windowFonts.HCourier.across;
  110. endFontWidths = windowFonts.HCourier.across;
  111. endX = 0;
  112. xOffset = 0; // Not horizontally scrolled.
  113. inputLineStart = -1;
  114. caretWidth = 2*GetSystemMetrics(SM_CXBORDER);
  115. clipboardInput = NULL;
  116. insertMode = TRUE;
  117. savedP1 = savedP2 = savedFirst = savedLast = 0;
  118. currentInputLine = -1;
  119. pageLine = 0;
  120. pageMode = pagePaused = FALSE;
  121. selFirstChar = selStartChar = selEndChar = 0;
  122. selFirstX = selStartX = selEndX = 0;
  123. selFirstLine = selStartLine = selEndLine = 0;
  124. trackingSelection = FALSE;
  125. selRootValid = FALSE;
  126. hasFocus = FALSE;
  127. // The characters in textBuffer are organised into lines. The start of each
  128. // line is recorded in lineBuffer, which is also a circular buffer so the
  129. // pointers into it are kept reduced using LINE_MASK. lineFirst identified
  130. // the first line stored. lineLast identifies the last line. Note that
  131. // the data making up a single line runs from where lineBuffer points either
  132. // to where a termination character is found in the text (this will be
  133. // a newline character) or until textLast. The latter case arises only for
  134. // a final non-terminated line of text. In general there will not be room
  135. // on the screen to display all the lines of text that are present.
  136. // lineVisible identifies the first line that can be displayed.
  137. //
  138. lineFirst = lineVisible = 0; // line buffer, contains one (empty)
  139. lineLast = 0; // line
  140. lineBuffer[0].position = 0;
  141. lineBuffer[0].up = 000;
  142. lineBuffer[0].height = 000; // get filled in when fonts are created
  143. lineBuffer[0].width = 000; // gets filled in when line painted.
  144. lineBuffer[0].address = textFirst;
  145. cLeft[0] = cRight[0] = 0;
  146. strncpy(cMid, programName, 31); cMid[31] = 0;
  147. GetSystemTime(&titleUpdateTime);
  148. inject_randomness((int)titleUpdateTime.wMilliseconds);
  149. cwin_display_date();
  150. strcpy(cwin_prompt_string, "> "); // A default prompt string.
  151. windowColour = GetSysColor(COLOR_WINDOW);
  152. textColour = GetSysColor(COLOR_WINDOWTEXT);
  153. highlightColour = GetSysColor(COLOR_HIGHLIGHT);
  154. highlightTextColour = GetSysColor(COLOR_HIGHLIGHTTEXT);
  155. #ifdef GRAPHICS_WINDOW
  156. graphicsShown = FALSE;
  157. graphicsWindow = new CGraphicsWindow;
  158. graphicsWindow->ShowWindow(SW_HIDE);
  159. #endif
  160. helpShown = FALSE;
  161. helpWindow = new CHelpWindow;
  162. CClientDC dc(helpWindow);
  163. dc.SelectObject(&helpWindow->helpFont);
  164. TEXTMETRIC mm;
  165. dc.GetTextMetrics(&mm);
  166. helpWindow->height = mm.tmExternalLeading + mm.tmAscent + mm.tmDescent;
  167. int widths[4];
  168. dc.GetCharWidth('X', 'X', widths);
  169. helpWindow->width = widths[0];
  170. WINDOWPLACEMENT wp;
  171. helpWindow->GetWindowPlacement(&wp);
  172. int screenWidth = GetSystemMetrics(SM_CXSCREEN);
  173. RECT *wr = &wp.rcNormalPosition;
  174. int left = wr->left;
  175. int cwidth = 80*helpWindow->width +
  176. 3*GetSystemMetrics(SM_CXBORDER) +
  177. 2*GetSystemMetrics(SM_CXFRAME) + 5;
  178. // Try to get the whole window onto the screen.
  179. if (left + cwidth > screenWidth)
  180. { left = screenWidth - cwidth;
  181. if (left < 0) left = 0;
  182. }
  183. int screenHeight = GetSystemMetrics(SM_CYSCREEN);
  184. int top = wr->top;
  185. int cheight = 25*helpWindow->height +
  186. GetSystemMetrics(SM_CYCAPTION) +
  187. GetSystemMetrics(SM_CYMENU) +
  188. 3*GetSystemMetrics(SM_CYBORDER) +
  189. 2*GetSystemMetrics(SM_CYFRAME) + 5;
  190. if (top + cheight > screenHeight)
  191. { top = screenHeight - cheight;
  192. if (top < 0) top = 0;
  193. }
  194. helpWindow->SetWindowPos(NULL,
  195. top, left,
  196. cwidth, cheight,
  197. SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER);
  198. helpWindow->ShowWindow(SW_HIDE);
  199. myTimer = SetTimer(1, 50, NULL); // 20 ticks per second -> message queue
  200. cwin_interrupt_pending = 0;
  201. complete = TRUE;
  202. }
  203. CMainWindow::~CMainWindow()
  204. {
  205. }
  206. void CMainWindow::OnDestroy()
  207. {
  208. #ifdef GRAPHICS_WINDOW
  209. graphicsWindow->viewpointWindow.DestroyWindow();
  210. graphicsWindow->DestroyWindow();
  211. // delete graphicsWindow;
  212. #endif
  213. if (myTimer!=0) KillTimer(myTimer);
  214. windowFonts.DeleteFonts();
  215. hasFinished = TRUE;
  216. }
  217. BEGIN_MESSAGE_MAP(CMainWindow, CFrameWnd)
  218. ON_WM_DESTROY()
  219. ON_WM_CHAR()
  220. ON_WM_HSCROLL()
  221. ON_WM_KEYDOWN()
  222. ON_WM_KILLFOCUS()
  223. ON_WM_LBUTTONDBLCLK()
  224. ON_WM_LBUTTONDOWN()
  225. ON_WM_LBUTTONUP()
  226. ON_WM_MBUTTONDBLCLK()
  227. ON_WM_MBUTTONDOWN()
  228. ON_WM_MBUTTONUP()
  229. ON_WM_MOUSEMOVE()
  230. // ON_WM_NCLBUTTONDOWN()
  231. // ON_WM_NCMBUTTONDOWN()
  232. // ON_WM_NCRBUTTONDOWN()
  233. ON_WM_PAINT()
  234. ON_WM_TIMER()
  235. ON_WM_RBUTTONDBLCLK()
  236. ON_WM_RBUTTONDOWN()
  237. ON_WM_RBUTTONUP()
  238. ON_WM_SETFOCUS()
  239. ON_WM_SIZE()
  240. ON_WM_MOVE()
  241. ON_WM_VSCROLL()
  242. // Now things on the menus...
  243. ON_COMMAND(IDM_READ, OnRead)
  244. ON_COMMAND(IDM_SAVEAS, OnSaveAs)
  245. ON_COMMAND(IDM_SAVESEL, OnSaveSel)
  246. ON_COMMAND(IDM_PRINT, OnPrint)
  247. ON_COMMAND(IDM_PRINTSEL, OnPrintSel)
  248. ON_COMMAND(IDM_TOFILE, OnToFile)
  249. ON_COMMAND(IDM_EXIT, OnExit)
  250. ON_COMMAND(IDM_CUT, OnCut)
  251. ON_COMMAND(IDM_COPY, OnCopy)
  252. ON_COMMAND(IDM_PASTE, OnPaste)
  253. ON_COMMAND(IDM_REINPUT, OnReInput)
  254. ON_COMMAND(IDM_SELECTALL, OnSelectAll)
  255. ON_COMMAND(IDM_CLEAR, OnClear)
  256. // ON_COMMAND(IDM_UNDO, OnUndo)
  257. ON_COMMAND(IDM_REDRAW, OnRedraw)
  258. ON_COMMAND(IDM_HOME, OnHome)
  259. ON_COMMAND(IDM_END, OnEnd)
  260. ON_COMMAND(IDM_FONT, OnFont)
  261. ON_COMMAND(IDM_RESET_FONT, OnResetFont)
  262. ON_COMMAND(IDM_RESET_WINDOW, OnResetWindow)
  263. ON_COMMAND(IDM_INTERRUPT, OnInterrupt)
  264. ON_COMMAND(IDM_BACKTRACE, OnBacktrace)
  265. ON_COMMAND(IDM_PAGEMODE, OnPageMode)
  266. #ifdef GRAPHICS_WINDOW
  267. ON_COMMAND(IDM_GRAPHICS, OnGraphics)
  268. ON_MESSAGE(WM_USER, OnGraphics1)
  269. #endif
  270. #ifndef COMMON
  271. ON_COMMAND_RANGE(IDM_FIRSTLOAD, IDM_FIRSTLOAD+199, OnLoadLibrary)
  272. ON_COMMAND_RANGE(IDS_FIRSTSWITCH, IDS_FIRSTSWITCH+199, OnSwitch)
  273. #endif
  274. END_MESSAGE_MAP()
  275. // Message handlers...
  276. #define caretY (lineBuffer[caretLine].position - \
  277. lineBuffer[lineVisible].position)
  278. #define caretDY (lineBuffer[caretLine].height)
  279. #define caretY1 (caretY+caretDY)
  280. void CMainWindow::OnPaint()
  281. {
  282. RECT update;
  283. inject_randomness((int)clock());
  284. if (!GetUpdateRect(&update, TRUE)) return;
  285. CPaintDC dc(this);
  286. dc.SetTextAlign(TA_BASELINE);
  287. dc.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK;
  288. dc.SetBkColor(windowColour);
  289. dc.SelectObject(windowFonts.Courier); currentFont = CH_COURIER;
  290. currentWidths = windowFonts.HCourier.across;
  291. int yBase = lineBuffer[lineVisible].position, line;
  292. HideCaret();
  293. for (line=lineVisible;; line=(line+1)&LINE_MASK)
  294. { int y = lineBuffer[line].position - yBase;
  295. int y1 = y+lineBuffer[line].up;
  296. int y2 = y+lineBuffer[line].height;
  297. if (y2 >= update.top)
  298. lineBuffer[line].width =
  299. PaintTextLine(&dc, -xOffset, y, y1, y2,
  300. lineBuffer[line].address,
  301. clientWidth, &windowFonts, 0);
  302. if (y > update.bottom+lineBuffer[line].up) break;
  303. if (line==lineLast) break;
  304. }
  305. ShowCaret();
  306. dc.SelectStockObject(SYSTEM_FONT);
  307. // Now I will worry about positioning the horizontal scroll thumb.
  308. int longestLine = 0;
  309. for (line=lineVisible;; line=(line+1)&LINE_MASK)
  310. { int y = lineBuffer[line].position - yBase;
  311. if (y > clientHeight) break;
  312. int w = lineBuffer[line].width;
  313. if (w > longestLine) longestLine = w;
  314. if (line==lineLast) break;
  315. }
  316. if (longestLine <= clientWidth)
  317. { if (xOffset != 0)
  318. {
  319. // Mess here - my response is to force the X offset to zero
  320. // and scroll the window to match - that will generally force a fresh
  321. // painting operation. This sort of thing can happen when there was a
  322. // long line in the buffer and the window had been scrolled right to view it,
  323. // and then by a sequence of vertical scrolling operations that line is
  324. // no longer on the screen and all the lines that are visible are short enough
  325. // to be shown in their entirity. It can also happen when somebody
  326. // increases the size of the window.
  327. ScrollWindow(xOffset, 0, NULL, NULL);
  328. xOffset = 0;
  329. SetScrollPos(SB_HORZ, 0, TRUE);
  330. }
  331. }
  332. else
  333. { int HScrollPos = (100*xOffset)/(longestLine-clientWidth/2);
  334. // If (for instance) the font had just cahnged or the windows size had been
  335. // enlarged you might have too little left visible on the screen - in such
  336. // cases I will forcibly scroll to correct things.
  337. if (HScrollPos > 100)
  338. { ScrollWindow(xOffset-longestLine+clientWidth/2, 0, NULL, NULL);
  339. xOffset = longestLine-clientWidth/2;
  340. HScrollPos = 100;
  341. }
  342. SetScrollPos(SB_HORZ, HScrollPos, TRUE);
  343. }
  344. CPoint caretPos(caretX-xOffset, caretY);
  345. SetCaretPos(caretPos);
  346. }
  347. // a, b and c are pointers into the circular text buffer, with a<=c
  348. // (logically). Test is a<=b<c. The mess here is because of wrap-
  349. // around with TEXT_MASK.
  350. BOOL betweenChar(int a, int b, int c)
  351. {
  352. if (a<=c) return (a<=b && b<c);
  353. else return (a<=b || b<c);
  354. }
  355. // Ditto but relative to the line buffer. Observe that the code I have is
  356. // identical, but I still like to keep the abstractions separate.
  357. BOOL betweenLine(int a, int b, int c)
  358. {
  359. if (a<=c) return (a<=b && b<c);
  360. else return (a<=b || b<c);
  361. }
  362. int CMainWindow::PaintTextLine(CDC *dc, int x,
  363. int topY, int y, int bottomY, int textChar,
  364. int width, FontArray *ff, int context)
  365. {
  366. // context = 0 painting to screen
  367. // = 1 printing whole of buffer
  368. // = 2 just printing selected region
  369. int nCount = 0;
  370. int needFont = (currentFont != CH_COURIER);
  371. int needColour = (currentColour != CH_BLACK);
  372. // I will buffer up to 80 characters of fixed-pitch output and display them
  373. // using a single call to TextOut. This is intended to reduce the cost of
  374. // calling the operating system for output.
  375. char buff[80];
  376. int bufp = 0, bufx = 0, bufy = 0;
  377. for (;;textChar=(textChar+1)&TEXT_MASK)
  378. {
  379. // I reset the X coordinate relating to the root, start and end of
  380. // any selection. This is necessary (for instance) after a font change,
  381. // and also means I do not have to re-calculate the X offsets when I am
  382. // inserting characters into the middle of the buffer.
  383. if (context == 0)
  384. { if (textChar == selFirstChar) selFirstX = x+xOffset;
  385. if (textChar == selStartChar) selStartX = x+xOffset;
  386. if (textChar == selEndChar) selEndX = x+xOffset;
  387. // Just after I have deleted a character (or two) I can have repositioned
  388. // the character but I will have lost track of the font active at its new
  389. // position, and thus of the font metrics, and the X coordinate that the
  390. // caret will be at. So although when inserting characters into the buffer
  391. // I can keep track of font & position I reset them here so that a
  392. // Paint operation following a delection puts things back in a coherent
  393. // state.
  394. if (textChar==caretChar)
  395. { caretX = x+xOffset;
  396. caretFontWidths = currentWidths;
  397. }
  398. if (textChar==icaretChar)
  399. { icaretX = x+xOffset;
  400. icaretFontWidths = currentWidths;
  401. }
  402. // Ditto for the end of the text buffer.
  403. if (textChar==textLast)
  404. { endX = x+xOffset;
  405. endFontWidths = currentWidths;
  406. break;
  407. }
  408. }
  409. else if (textChar==textLast) break;
  410. // I will stop if I either find a newline character or I reach the end
  411. // of the buffer. In each case I will have sorted out how long the line
  412. // I was displaying was.
  413. int c = textBuffer[textChar];
  414. // I permit ESC characters in the buffer, but I will display them as $
  415. // following an ancient tradition. Other control characters are not really
  416. // permitted and could lead to mangled display.
  417. if (c == 0x1b) c = '$';
  418. int w = currentWidths[c & 0xff];
  419. BOOL inSelection = betweenChar(selStartChar, textChar, selEndChar);
  420. if (context==0 && inSelection &&
  421. (currentColour==CH_BLACK || currentColour==CH_HIGHLIGHT))
  422. { needColour = FALSE;
  423. currentColour = CH_HIGHLIGHT;
  424. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  425. dc->SetTextColor(highlightTextColour);
  426. dc->SetBkColor(highlightColour);
  427. }
  428. else if (!inSelection)
  429. {
  430. // If I am outside the selected region and context==2 (printing selection)
  431. // then I map characters onto blanks so thet they will not appear in the
  432. // output.
  433. if (context == 2) c = ' ';
  434. if (currentColour==CH_HIGHLIGHT)
  435. { needColour = FALSE;
  436. currentColour = CH_BLACK;
  437. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  438. dc->SetTextColor(RGB(0,0,0));
  439. dc->SetBkColor(windowColour);
  440. }
  441. }
  442. switch (c & 0xff)
  443. {
  444. case '\n':
  445. break;
  446. case CH_RED:
  447. needColour = FALSE;
  448. if (currentColour == c) continue;
  449. currentColour = c;
  450. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  451. dc->SetTextColor(RGB(255,0,0));
  452. dc->SetBkColor(windowColour);
  453. continue;
  454. case CH_BLUE:
  455. needColour = FALSE;
  456. if (currentColour == c) continue;
  457. currentColour = c;
  458. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  459. dc->SetTextColor(RGB(0,0,255));
  460. dc->SetBkColor(windowColour);
  461. continue;
  462. case CH_BLACK:
  463. if (inSelection && context==0)
  464. { c = CH_HIGHLIGHT;
  465. needColour = FALSE;
  466. if (currentColour == c) continue;
  467. currentColour = c;
  468. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  469. dc->SetTextColor(highlightTextColour);
  470. dc->SetBkColor(highlightColour);
  471. }
  472. else
  473. { needColour = FALSE;
  474. if (currentColour == c) continue;
  475. currentColour = c;
  476. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  477. dc->SetTextColor(RGB(0,0,0));
  478. dc->SetBkColor(windowColour);
  479. }
  480. continue;
  481. case CH_GRAY:
  482. needColour = FALSE;
  483. if (currentColour == c) continue;
  484. currentColour = c;
  485. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  486. dc->SetTextColor(RGB(128,128,128));
  487. dc->SetBkColor(windowColour);
  488. continue;
  489. case CH_COURIER:
  490. needFont = FALSE;
  491. if (currentFont == c) continue;
  492. currentFont = c;
  493. currentWidths = ff->HCourier.across;
  494. dc->SelectObject(ff->Courier);
  495. continue;
  496. case CH_ROMAN:
  497. needFont = FALSE;
  498. if (currentFont == c) continue;
  499. currentFont = c;
  500. currentWidths = ff->HRoman.across;
  501. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  502. dc->SelectObject(ff->Roman);
  503. continue;
  504. case CH_BOLD:
  505. needFont = FALSE;
  506. if (currentFont == c) continue;
  507. currentFont = c;
  508. currentWidths = ff->HBold.across;
  509. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  510. dc->SelectObject(ff->Bold);
  511. continue;
  512. case CH_ITALIC:
  513. needFont = FALSE;
  514. if (currentFont == c) continue;
  515. currentFont = c;
  516. currentWidths = ff->HItalic.across;
  517. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  518. dc->SelectObject(ff->Italic);
  519. continue;
  520. case CH_SYMBOL:
  521. needFont = FALSE;
  522. if (currentFont == c) continue;
  523. currentFont = c;
  524. currentWidths = ff->HSymbol.across;
  525. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  526. dc->SelectObject(ff->Symbol);
  527. continue;
  528. case CH_Roman:
  529. needFont = FALSE;
  530. if (currentFont == c) continue;
  531. currentFont = c;
  532. currentWidths = ff->Hroman.across;
  533. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  534. dc->SelectObject(ff->roman);
  535. continue;
  536. case CH_Bold:
  537. needFont = FALSE;
  538. if (currentFont == c) continue;
  539. currentFont = c;
  540. currentWidths = ff->Hbold.across;
  541. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  542. dc->SelectObject(ff->bold);
  543. continue;
  544. case CH_Italic:
  545. needFont = FALSE;
  546. if (currentFont == c) continue;
  547. currentFont = c;
  548. currentWidths = ff->Hitalic.across;
  549. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  550. dc->SelectObject(ff->italic);
  551. continue;
  552. case CH_Symbol:
  553. needFont = FALSE;
  554. if (currentFont == c) continue;
  555. currentFont = c;
  556. currentWidths = ff->Hsymbol.across;
  557. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  558. dc->SelectObject(ff->symbol);
  559. continue;
  560. default:
  561. // The start of every line should be in regular-sized Courier and
  562. // in black. But rather than calling Windows to force that selection
  563. // on every line (I hypothesize that selecting a fond and a colour
  564. // into my device context may be costly) I only do so when I find I have
  565. // a character to display and then only in cases where what was left over
  566. // from whatever was last printed was somehow different.
  567. if (context==0 && needColour && inSelection)
  568. { if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  569. dc->SetTextColor(highlightTextColour);
  570. dc->SetBkColor(highlightColour);
  571. currentColour = CH_BLUE;
  572. needColour = FALSE;
  573. }
  574. else if (needColour)
  575. { if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  576. dc->SetTextColor(RGB(0,0,0));
  577. dc->SetBkColor(windowColour);
  578. currentColour = CH_BLACK;
  579. needColour = FALSE;
  580. }
  581. if (needFont)
  582. { dc->SelectObject(ff->Courier);
  583. currentFont = CH_COURIER;
  584. currentWidths = ff->HCourier.across;
  585. needFont = FALSE;
  586. }
  587. if (c == '\t')
  588. { c = ' ';
  589. w = currentWidths[c]*(8-(nCount&7));
  590. nCount = 7;
  591. }
  592. else if (c < 32)
  593. { w = currentWidths['^'];
  594. // There is a curiosity here, that for the moment I am going to leave alone.
  595. // If one has a control character in the buffer it is displayed as something
  596. // like ^I (with the '^' as a separate character and the 'I' a normal letter).
  597. // If this is within a selection the 'I' is shown in inverse video as usual
  598. // but the '^' is not. This could perhaps be seen as a way of making it
  599. // possible to distinguish between the control character and a regular pair
  600. // of characters spelt the same.
  601. if (x+w>=0 && x<width)
  602. { if (bufp>=sizeof(buff))
  603. dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  604. if (bufp == 0) bufx = x, bufy = y;
  605. buff[bufp++] = '^';
  606. }
  607. x += currentWidths['^'];
  608. c = c | 0x40;
  609. w = currentWidths[c & 0xff];
  610. nCount++;
  611. }
  612. if (inSelection && context==0)
  613. { CBrush b(highlightColour);
  614. CBrush *oldbrush = dc->SelectObject(&b);
  615. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  616. dc->SelectStockObject(NULL_PEN);
  617. dc->Rectangle(x, topY, x+w+1, bottomY+1);
  618. dc->SelectObject(oldbrush);
  619. }
  620. if (x+w>=0 && x<width)
  621. { if (currentFont == CH_COURIER)
  622. { if (bufp>=sizeof(buff))
  623. dc->TextOut(bufx, bufy, buff, bufp), bufp = 0;
  624. if (bufp == 0) bufx = x, bufy = y;
  625. buff[bufp++] = c;
  626. }
  627. else
  628. { if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
  629. buff[0] = c;
  630. dc->TextOut(x, y, buff, 1);
  631. }
  632. }
  633. x += w;
  634. nCount++;
  635. continue;
  636. }
  637. break;
  638. }
  639. if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp);
  640. // I am NOT happy about the next few lines -- they fill an area in background
  641. // colour out across to the end of the line. I do this because otherwise
  642. // despite lots of calls to HideCaret I seem to observe remains of the caret
  643. // displayed after I have made a line shorter. I do not understand why this
  644. // happens but hope that what follows will work around the problem.
  645. if (context==0)
  646. { CBrush b(windowColour);
  647. CBrush *oldbrush = dc->SelectObject(&b);
  648. dc->SelectStockObject(NULL_PEN);
  649. dc->Rectangle(x, topY,
  650. width, bottomY+1);
  651. dc->SelectObject(oldbrush);
  652. }
  653. return x + xOffset;
  654. }
  655. int cwin_linelength = 80;
  656. void CMainWindow::OnSize(UINT nType, int cx, int cy)
  657. {
  658. WINDOWPLACEMENT wp;
  659. GetWindowPlacement(&wp);
  660. RECT *wr = &wp.rcNormalPosition;
  661. clientWidth = cx;
  662. clientHeight = cy;
  663. if (nType != SIZE_MINIMIZED)
  664. cwin_linelength = (clientWidth-5) / windowFonts.HCourier.across['X'];
  665. theApp.WriteProfileInt("MainWindow", "ScreenWidth", wr->right-wr->left);
  666. theApp.WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top);
  667. theApp.WriteProfileInt("MainWindow", "LineLength", cwin_linelength);
  668. if (caretVisible)
  669. { OnVScroll(SB_FOR_CARET, 0, NULL);
  670. OnHScroll(SB_FOR_CARET, 0, NULL);
  671. }
  672. OnHScroll(SB_REFRESH_THUMB, 0, NULL);
  673. OnVScroll(SB_REFRESH_THUMB, 0, NULL);
  674. }
  675. void CMainWindow::OnMove(int x, int y)
  676. {
  677. WINDOWPLACEMENT wp;
  678. GetWindowPlacement(&wp);
  679. RECT *wr = &wp.rcNormalPosition;
  680. theApp.WriteProfileInt("MainWindow", "ScreenLeft", wr->left);
  681. theApp.WriteProfileInt("MainWindow", "ScreenTop", wr->top);
  682. }
  683. void CMainWindow::OnResetWindow()
  684. {
  685. int screenWidth = GetSystemMetrics(SM_CXSCREEN);
  686. WINDOWPLACEMENT wp;
  687. GetWindowPlacement(&wp);
  688. RECT *wr = &wp.rcNormalPosition;
  689. int left = wr->left;
  690. int cwidth = 80*windowFonts.HCourier.across['X'] +
  691. 3*GetSystemMetrics(SM_CXBORDER) +
  692. 2*GetSystemMetrics(SM_CXFRAME) +
  693. GetSystemMetrics(SM_CXVSCROLL) + 5;
  694. // Try to get the whole window onto the screen.
  695. if (left + cwidth > screenWidth)
  696. { left = screenWidth - cwidth;
  697. if (left < 0) left = 0;
  698. }
  699. SetWindowPos(NULL,
  700. left, wr->top,
  701. cwidth, (wr->bottom - wr->top),
  702. SWP_SHOWWINDOW | SWP_NOZORDER);
  703. UpdateWindow();
  704. cwin_linelength = 80;
  705. theApp.WriteProfileInt("MainWindow", "ScreenLeft", left);
  706. theApp.WriteProfileInt("MainWindow", "ScreenWidth", cwidth);
  707. theApp.WriteProfileInt("MainWindow", "ScreenTop", wr->top);
  708. theApp.WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top);
  709. theApp.WriteProfileInt("MainWindow", "LineLength", cwin_linelength);
  710. }
  711. void CMainWindow::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pNctl)
  712. {
  713. // I will start by computing the total height of all the stuff I have in
  714. // my text buffer. At the very worst I only save a couple of thousand lines,
  715. // so adding up their heights again will not be TOO painful, I hope.
  716. int VScrollPos, movedUp=0, i, w;
  717. int aboveTop = -LineY(lineFirst);
  718. int totalHeight = aboveTop + LineY(lineLast) + LineDY(lineLast);
  719. inject_randomness(totalHeight);
  720. // If I can make everything visible I will do that regardless of anything
  721. // the user tries to do.
  722. if (totalHeight <= clientHeight)
  723. { lineVisible = lineFirst;
  724. movedUp = aboveTop;
  725. VScrollPos = 0;
  726. }
  727. else
  728. { switch (nSBCode)
  729. {
  730. case SB_ENDSCROLL:
  731. return;
  732. case SB_REFRESH_THUMB:
  733. // In case the window has just been enlarged I check to see if I can scroll
  734. // up a bit until one more scroll up would make the last line of my text
  735. // drop off the (new) bottom of the window.
  736. while (lineVisible!=lineFirst)
  737. { int lv = (lineVisible-1)&LINE_MASK;
  738. int nAbove = lineBuffer[lv].position -
  739. lineBuffer[lineFirst].position;
  740. if (totalHeight > nAbove+clientHeight) break;
  741. lineVisible = lv;
  742. w = LineDY(lv);
  743. movedUp += w;
  744. aboveTop -= w;
  745. }
  746. break;
  747. case SB_THUMBPOSITION:
  748. case SB_THUMBTRACK:
  749. i = (nPos*(totalHeight-clientHeight))/100;
  750. if (i > aboveTop)
  751. { while (totalHeight > aboveTop+clientHeight &&
  752. i > aboveTop)
  753. { w = LineDY(lineVisible);
  754. aboveTop += w;
  755. movedUp -= w;
  756. lineVisible = (lineVisible+1) & LINE_MASK;
  757. }
  758. }
  759. else
  760. { i += LineDY(lineVisible);
  761. while (lineVisible != lineFirst &&
  762. i <= aboveTop)
  763. { lineVisible = (lineVisible-1) & LINE_MASK;
  764. w = LineDY(lineVisible);
  765. aboveTop -= w;
  766. movedUp += w;
  767. }
  768. }
  769. break;
  770. case SB_FOR_CARET:
  771. if (LineY(caretLine)+LineDY(caretLine) > clientHeight)
  772. { while (totalHeight > aboveTop+clientHeight &&
  773. LineY(caretLine)+LineDY(caretLine) > clientHeight)
  774. { w = LineDY(lineVisible);
  775. aboveTop += w;
  776. movedUp -= w;
  777. lineVisible = (lineVisible+1) & LINE_MASK;
  778. }
  779. }
  780. else
  781. { while (lineVisible != lineFirst &&
  782. LineY(caretLine) < 0)
  783. { lineVisible = (lineVisible-1) & LINE_MASK;
  784. w = LineDY(lineVisible);
  785. aboveTop -= w;
  786. movedUp += w;
  787. }
  788. }
  789. break;
  790. case SB_LINEDOWN:
  791. // If the bottom line of text is already visible I will not scroll down
  792. // any further.
  793. if (totalHeight > aboveTop+clientHeight)
  794. { lineVisible = (lineVisible+1) & LINE_MASK;
  795. w = lineBuffer[lineVisible].height;
  796. aboveTop += w;
  797. movedUp = -w;
  798. }
  799. break;
  800. case SB_LINEUP:
  801. if (lineVisible != lineFirst)
  802. { w = lineBuffer[lineVisible].height;
  803. aboveTop -= w;
  804. movedUp = w;
  805. lineVisible = (lineVisible-1) & LINE_MASK;
  806. }
  807. break;
  808. // When asked to scroll be a "page" I in fact jump by around 0.7 times
  809. // the number of lines visible on a page.
  810. case SB_PAGEDOWN:
  811. while (totalHeight > aboveTop+clientHeight &&
  812. -10*movedUp < 7*clientHeight)
  813. { lineVisible = (lineVisible+1) & LINE_MASK;
  814. w = lineBuffer[lineVisible].height;
  815. aboveTop += w;
  816. movedUp -= w;
  817. }
  818. break;
  819. case SB_PAGEUP:
  820. while (lineVisible != lineFirst &&
  821. 10*movedUp < 7*clientHeight)
  822. { w = lineBuffer[lineVisible].height;
  823. aboveTop -= w;
  824. movedUp += w;
  825. lineVisible = (lineVisible-1) & LINE_MASK;
  826. }
  827. break;
  828. }
  829. if (lineVisible == lineFirst) VScrollPos = 0;
  830. else if (totalHeight <= aboveTop+clientHeight) VScrollPos = 100;
  831. else VScrollPos = (100*aboveTop)/(totalHeight-clientHeight);
  832. }
  833. // If the user had just dragged the scroll bar I will tend to leave the
  834. // scroll bar thumb where the user put it. However I will make an exception
  835. // if the user's drag caused me to go to one or other extreme position, since
  836. // I think it would be confusing to have the window totally fully scrolled
  837. // and the thumb not extremal in its bar. But at all intermediate positions
  838. // it is kindest to let it settle where the user put it!
  839. if (nSBCode == SB_THUMBPOSITION ||
  840. nSBCode == SB_THUMBTRACK)
  841. { if (VScrollPos == 0 || VScrollPos == 100)
  842. nPos = VScrollPos;
  843. SetScrollPos(SB_VERT, nPos, TRUE);
  844. }
  845. else SetScrollPos(SB_VERT, VScrollPos, TRUE);
  846. if (movedUp != 0)
  847. { HideCaret();
  848. ScrollWindow(0, movedUp, NULL, NULL);
  849. ShowCaret();
  850. }
  851. // If the window is minimized then I will not change the status of apparent
  852. // visibility of the caret. This is important because if the caret becomes
  853. // marked as invisible because (for instance) the window is of zero size then
  854. // it will remain invisible and the window will not be scrolled ever, leaving
  855. // the text buffer to over-fill and hang things up.
  856. if (IsIconic()) return;
  857. else if (caretY >= 0 && caretY1 <= clientHeight &&
  858. caretX-xOffset >= 0 && caretX-xOffset < clientWidth)
  859. caretVisible = TRUE;
  860. else caretVisible = FALSE;
  861. }
  862. void CMainWindow::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *PCntl)
  863. {
  864. // Each time I hit the horizontal scroll-bar I will check to find how
  865. // long the longest (partially) visible line is.
  866. int yBase = lineBuffer[lineVisible].position;
  867. int line, longestLine = 0;
  868. for (line=lineVisible;; line=(line+1)&LINE_MASK)
  869. { int y = lineBuffer[line].position - yBase;
  870. if (y > clientHeight) break;
  871. int w = lineBuffer[line].width;
  872. if (w > longestLine) longestLine = w;
  873. if (line==lineLast) break;
  874. }
  875. // If everything fits I will just make sure I am scrolled fully left. Well as
  876. // a matter of caution I do not force things that way if the request was
  877. // to make the caret visible, since maybe if the screen is not quite up to
  878. // date it may seem that I could afford to scroll fully left but that would
  879. // in fact hide the caret.
  880. if (longestLine < clientWidth && nSBCode != SB_FOR_CARET)
  881. { if (xOffset != 0)
  882. { HideCaret();
  883. ScrollWindow(xOffset, 0, NULL, NULL);
  884. ShowCaret();
  885. }
  886. xOffset = 0;
  887. SetScrollPos(SB_HORZ, 0, TRUE);
  888. return;
  889. }
  890. int moveLeft = 0, w;
  891. switch (nSBCode)
  892. {
  893. case SB_ENDSCROLL:
  894. return;
  895. case SB_REFRESH_THUMB:
  896. break;
  897. case SB_THUMBPOSITION:
  898. case SB_THUMBTRACK:
  899. w = (nPos*(longestLine-clientWidth/2))/100;
  900. moveLeft = xOffset - w;
  901. xOffset = w;
  902. break;
  903. case SB_FOR_CARET:
  904. w = windowFonts.HCourier.across['X'];
  905. if (caretX-xOffset < 5*w)
  906. moveLeft = w*((clientWidth/2 - (caretX-xOffset))/w);
  907. else if (caretX-xOffset >= clientWidth)
  908. moveLeft = -w*((caretX - xOffset - clientWidth)/w + 4);
  909. xOffset -= moveLeft;
  910. break;
  911. // Small jumps are by the width of the letter X in the biggest fixed pitch
  912. // font that I have selected. Thus if you select a big font you will get
  913. // coarser contol over horizontal scrolling.
  914. case SB_LINERIGHT:
  915. w = windowFonts.HCourier.across['X'];
  916. xOffset += w;
  917. moveLeft = -w;
  918. break;
  919. case SB_LINELEFT:
  920. w = windowFonts.HCourier.across['X'];
  921. xOffset -= w;
  922. moveLeft = w;
  923. break;
  924. // Big jumps are by roughly (2/3) the width of the main Window
  925. case SB_PAGERIGHT:
  926. w = (2*clientWidth)/3;
  927. xOffset += w;
  928. moveLeft = -w;
  929. break;
  930. case SB_PAGELEFT:
  931. w = (2*clientWidth)/3;
  932. xOffset -= w;
  933. moveLeft = w;
  934. break;
  935. }
  936. if (xOffset < 0)
  937. { moveLeft += xOffset;
  938. xOffset = 0;
  939. }
  940. // I will permit scrolls to the right to a stage where the longest line
  941. // is (1/2) across the screen. But no further and especially I will not
  942. // permit scrolls right that cause the text on the screen to vanish
  943. // totally. The limit I use here is somewhat of an arbitrary choice. If a
  944. // "scroll page right" request looked as if it would move beyond this I will
  945. // clip the scroll.
  946. w = longestLine - clientWidth/2;
  947. if (w <= 0)
  948. { moveLeft += xOffset;
  949. xOffset = 0;
  950. w = 1;
  951. }
  952. else if (xOffset>w)
  953. { moveLeft += xOffset - w;
  954. xOffset = w;
  955. }
  956. int HScrollPos = (100*xOffset)/w;
  957. if (HScrollPos > 100) HScrollPos = 100;
  958. if ((nSBCode != SB_THUMBPOSITION &&
  959. nSBCode != SB_THUMBTRACK) ||
  960. HScrollPos == 0 ||
  961. HScrollPos == 100) SetScrollPos(SB_HORZ, HScrollPos, TRUE);
  962. else SetScrollPos(SB_HORZ, nPos, TRUE);
  963. if (moveLeft != 0)
  964. { HideCaret();
  965. ScrollWindow(moveLeft, 0, NULL, NULL);
  966. ShowCaret();
  967. }
  968. }
  969. void CMainWindow::OnSetFocus(CWnd *pOldWnd)
  970. {
  971. CreateSolidCaret(caretWidth, windowFonts.HCourier.height);
  972. ShowCaret();
  973. SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);
  974. selectScrollSpeed = 0;
  975. hasFocus = TRUE; /* True just after focus has returned to this window */
  976. }
  977. void CMainWindow::OnKillFocus(CWnd *pNewWnd)
  978. {
  979. ::DestroyCaret();
  980. hasFocus = FALSE;
  981. }
  982. // Sometime I find reading documentation quite hard. I had really thought that
  983. // when one selected an application (eg using ALT+TAB) that would give the
  984. // input focus to that application's main window. But it appears
  985. // (experimentally) that this is not always true, and I found myself with
  986. // a window with hard blue title bar but no input focus. It seems (?) that
  987. // the application has been "activated", so here I ensure that when that
  988. // happens it grabs the focus. Elsewhere you will find that whenever I see a
  989. // mouse button depressed I grab the focus too (just to be on the safe side).
  990. void CMainWindow::OnActivate(UINT state, CWnd *pWnd, BOOL minim)
  991. {
  992. if (state != WA_INACTIVE) SetFocus();
  993. }
  994. // When I put a character into the text buffer I need to worry about purging
  995. // old characters when the buffer wraps around. That may have an effect on
  996. // what is visible on the screen, or on a selected region. If I insert a
  997. // character and I am at the foot of a page I may need to scroll up. All
  998. // in all the funny cases are quite messy here!
  999. void CMainWindow::cwin_putchar(int ch)
  1000. {
  1001. if (hasFinished) return;
  1002. BOOL caretAtEnd = (caretChar == textLast);
  1003. if (ch == '\n')
  1004. {
  1005. // If the caret is at the end of the text and if "page mode" is enabled
  1006. // then putting a newline into the text buffer will sometimes lead to
  1007. // a pause....
  1008. BOOL usualTitle = TRUE;
  1009. // The test here says pause if
  1010. // (a) I am in page mode
  1011. // (b) lineVisible==pageLine. Well lineFirst is the line that is now in
  1012. // danger of being about to scroll off the top of the screen, and so
  1013. // pageLine will identify the current line to be "protected" from that
  1014. // fate.
  1015. // (c) caretLine vs clientHeight: This tries to test if scrolling would be
  1016. // called for if the caret moved down a line (hence the 2*LineDY()).
  1017. // In the pause case I just loop polling the window manager until something
  1018. // magic happens. In general I can do the correct magic by making a suitable
  1019. // selection of events do "pageLine = caretLine;"
  1020. //
  1021. // Actually it is nastier than that, because lineVisible may not be up to
  1022. // date - so I have to work out what lineVisible WOULD be in the relevant
  1023. // situation by having code rather line that in OnVScroll/SB_FOR_CARET.
  1024. while (pageMode)
  1025. { int lv = lineVisible, w;
  1026. int aboveTop = -LineY(lineFirst);
  1027. int totalHeight = aboveTop + LineY(lineLast) + LineDY(lineLast);
  1028. if (totalHeight <= clientHeight) lv = lineFirst;
  1029. else while (totalHeight > aboveTop+clientHeight &&
  1030. LineY(caretLine)+LineDY(caretLine) > clientHeight)
  1031. { w = LineDY(lv);
  1032. aboveTop += w;
  1033. lv = (lv+1) & LINE_MASK;
  1034. }
  1035. if (lv==pageLine &&
  1036. (LineY(caretLine)+2*LineDY(caretLine) > clientHeight))
  1037. { if (usualTitle)
  1038. { usualTitle = FALSE;
  1039. pagePaused = TRUE;
  1040. // BeginWaitCursor();
  1041. // For reasons that I do not fully understand a "wait" cursor established here
  1042. // gets turned back into the usual arrow one before I want it to. I suspect
  1043. // maybe that whenever the system re-draws the cursor it does so using the
  1044. // version that is set as default for the application? Anyway the effect is
  1045. // that I get an apparantly inconsistent behaviour, and at present I think
  1046. // that keeping the cursor consistently an arrow is better than letting it
  1047. // flash incoherently between that and an hourglass.
  1048. SetWindowText("(page-mode) Type any key to continue");
  1049. }
  1050. // Here I will force the caret to be visible and poll the window manager
  1051. if (!hasFinished)
  1052. { OnVScroll(SB_FOR_CARET, 0, NULL);
  1053. OnHScroll(SB_FOR_CARET, 0, NULL);
  1054. cwin_poll_window_manager();
  1055. }
  1056. if (hasFinished) return;
  1057. continue;
  1058. }
  1059. else break;
  1060. }
  1061. if (!usualTitle)
  1062. { SetWindowText(mainTitle);
  1063. // EndWaitCursor();
  1064. }
  1065. pagePaused = FALSE;
  1066. textBuffer[textLast] = '\n';
  1067. int n = (textLast+1) & TEXT_MASK;
  1068. if (n == textFirst) WrapTextBuffer(TRUE);
  1069. textLast = n;
  1070. // When I see a newline I may need to wrap the line buffer, discarding the
  1071. // oldest saved line.
  1072. int nextPos = lineBuffer[lineLast].position +
  1073. lineBuffer[lineLast].height;
  1074. n = (lineLast+1) & LINE_MASK;
  1075. if (n == lineFirst) WrapTextBuffer(TRUE);
  1076. lineLast = n;
  1077. lineBuffer[lineLast].position = nextPos;
  1078. lineBuffer[lineLast].up = windowFonts.HCourier.up;
  1079. lineBuffer[lineLast].height = windowFonts.HCourier.up +
  1080. windowFonts.HCourier.down;
  1081. lineBuffer[lineLast].width = 0;
  1082. lineBuffer[lineLast].address = textLast;
  1083. endFontWidths = windowFonts.HCourier.across;
  1084. endX = 0;
  1085. if (caretAtEnd)
  1086. { caretChar = icaretChar = textLast;
  1087. caretLine = icaretLine = lineLast;
  1088. caretX = icaretX = 0;
  1089. caretFontWidths = icaretFontWidths = windowFonts.HCourier.across;
  1090. }
  1091. }
  1092. else
  1093. { textBuffer[textLast] = ch;
  1094. int n = (textLast+1) & TEXT_MASK;
  1095. if (n == textFirst) WrapTextBuffer(TRUE);
  1096. // I invalidate all the way to the right of the line just to be on the
  1097. // safe side. This is probably excessive... but equally the painting in of
  1098. // the solid blank to the right of any real text will not be too expensive.
  1099. CRect cr(lineBuffer[lineLast].width-xOffset, LineY(lineLast),
  1100. clientWidth, LineY(lineLast)+LineDY(lineLast));
  1101. InvalidateRect(&cr); // Should I hide the caret here?
  1102. textLast = n;
  1103. endX += endFontWidths[ch];
  1104. lineBuffer[lineLast].width += endFontWidths[ch];
  1105. if (caretAtEnd)
  1106. { caretChar = icaretChar = textLast;
  1107. caretX += caretFontWidths[ch];
  1108. icaretX = caretX;
  1109. }
  1110. }
  1111. }
  1112. // This code inserts characters wherever the caret happens to be, typically
  1113. // in the middle of the text buffer. This returns TRUE if it succeeds. The
  1114. // case when it might fail is if the text buffer is full and the caret is on
  1115. // the first line. Then to make room for the next stuff the first line would
  1116. // need to be discarded, but I can not do that without loss of the caret.
  1117. //
  1118. // UnTypeAhead() is used to push characters into the type-ahead buffer if
  1119. // a newline is inserted into the current line of input (ie between
  1120. // inputLineStart and textLast). It returns FALSE if pushing characters
  1121. // flushed out previously typed stuff.
  1122. BOOL CMainWindow::UnTypeAhead(int ch)
  1123. {
  1124. int w = (typeAheadP1-1)&typeAheadBufferMask;
  1125. typeAheadBuffer[w] = ch;
  1126. typeAheadP1 = w;
  1127. if (typeAheadP1 == typeAheadP2)
  1128. { typeAheadP2 = (typeAheadP2-1)&typeAheadBufferMask;
  1129. return FALSE; // Inserting pushes out something else
  1130. }
  1131. else return TRUE;
  1132. }
  1133. BOOL CMainWindow::InsertAtCaret(char *s, int n)
  1134. {
  1135. // I will do large inserts in 60-character chunks. That is so I can have
  1136. // a limited size circular buffer (64 chars here) for use when copying
  1137. // stuff down the buffer. Huge inserts would benefit from a larger chunk-size
  1138. // here, at the cost of using more memory for the copy buffer.
  1139. while (n > 60)
  1140. { if (!InsertAtCaret(s, 60)) return FALSE;
  1141. s += 60;
  1142. n -= 60;
  1143. }
  1144. if (n == 0) return TRUE;
  1145. int oldCaretLine = caretLine, oldCaretChar = caretChar;
  1146. BOOL startEnd = (inputLineStart == textLast);
  1147. int i, p = textLast;
  1148. // Firstly I will make sure that there is room for the inserted text by
  1149. // stepping on n characters from the current end of the text buffer and
  1150. // wraping to free up the space I walk over. If this were to collide with
  1151. // the caret position I would be in a MESS so in such cases I will not
  1152. // perform the insert & will return a failure flag.
  1153. for (i=0; i<n; i++)
  1154. { p = (p+1)&TEXT_MASK;
  1155. if (p == textFirst)
  1156. { if (caretLine == lineFirst) return FALSE; // fail
  1157. else WrapTextBuffer(FALSE);
  1158. }
  1159. }
  1160. // Also I need to ensure that there is room for the number of new lines
  1161. // that I will insert. It will be rare that this is a problem, I suspect.
  1162. // Also beware here if the caret is positioned on the top line of the
  1163. // buffer.
  1164. int k = 0;
  1165. for (i=0; i<n; i++) if (s[i] == '\n') k++;
  1166. p = lineLast;
  1167. for (i=0; i<k; i++)
  1168. { p = (p+1)&LINE_MASK;
  1169. if (p == lineFirst)
  1170. { if (caretLine == lineFirst) return FALSE; // fail
  1171. else WrapTextBuffer(FALSE);
  1172. }
  1173. }
  1174. // Now I know that there is room for the insertion, I will "just" need to
  1175. // copy characters up through the text buffer.
  1176. char circle[64], circleFlags[64];
  1177. memcpy(circle, s, n);
  1178. memset(circleFlags, 0, n);
  1179. int inP = n, outP = 0;
  1180. // (line,p) is where I read characters from the buffer, while (line1,p1)
  1181. // is where I put them back. Certainly at the start, and quite often all
  1182. // the way through these values will remain in step. They may drift apart if
  1183. // previous deletions within lines lave left gaps in the buffer between the
  1184. // end of one line and the start of the next.
  1185. int line = caretLine;
  1186. p = caretChar;
  1187. int line1 = line, p1 = p;
  1188. while (p != textLast)
  1189. { int c = textBuffer[p];
  1190. circle[inP] = c;
  1191. circleFlags[inP] = 0;
  1192. if (p == caretChar) circleFlags[inP] |= 1;
  1193. // I will leave inputLineStart where it is if it lies just before the
  1194. // caret position at the start of the insert operation.
  1195. if (p == inputLineStart && p != oldCaretChar) circleFlags[inP] |= 2;
  1196. if (p == icaretChar) circleFlags[inP] |= 4;
  1197. if (c == '\n')
  1198. { line = (line+1)&LINE_MASK;
  1199. p = lineBuffer[line].address;
  1200. }
  1201. else p = (p+1)&TEXT_MASK;
  1202. inP = (inP+1)&63;
  1203. int c1 = circle[outP];
  1204. textBuffer[p1] = c1;
  1205. if (circleFlags[outP] & 1) caretChar = p1, caretLine = line1;
  1206. if (circleFlags[outP] & 2) inputLineStart = p1;
  1207. if (circleFlags[outP] & 4) icaretChar = p1, icaretLine = line1;
  1208. p1 = (p1+1)&TEXT_MASK;
  1209. outP = (outP+1)&63;
  1210. // Now if I had just inserted a newline I need to fix up the pointers
  1211. // from the line buffer into the text buffer.
  1212. if (c1 == '\n')
  1213. { if (line == line1) // Here I need to shuffle lines up
  1214. { int a = p1, line2=line;
  1215. lineLast = (lineLast+1)&LINE_MASK;
  1216. for (;;)
  1217. { line2 = (line2+1)&LINE_MASK;
  1218. int w = lineBuffer[line2].address;
  1219. lineBuffer[line2].address = a;
  1220. if (line2==lineLast) break;
  1221. a = w;
  1222. }
  1223. line = (line+1)&LINE_MASK;
  1224. }
  1225. line1 = (line1+1)&LINE_MASK;
  1226. lineBuffer[line1].address = p1;
  1227. }
  1228. }
  1229. while (inP != outP)
  1230. { int c1 = circle[outP];
  1231. textBuffer[p1] = c1;
  1232. if (circleFlags[outP] & 1) caretChar = p1, caretLine = line1;
  1233. if (circleFlags[outP] & 2) inputLineStart = p1;
  1234. if (circleFlags[outP] & 4) icaretChar = p1, icaretLine = line1;
  1235. if (c1 == '\n')
  1236. { line1 = (line1+1)&LINE_MASK;
  1237. lineBuffer[line1].address = p1;
  1238. }
  1239. p1 = (p1+1)&TEXT_MASK;
  1240. outP = (outP+1)&63;
  1241. }
  1242. textLast = p1;
  1243. lineLast = line1;
  1244. // Here it may be that a newline had been inserted within the line that
  1245. // was being prepared for input. If so, stuff after that newline must be
  1246. // pushed back into the type-ahead buffer. When this happens it will always
  1247. // be the case that the text in the buffer after inputLineStart will form
  1248. // a compact block so I can just copy chars backwards
  1249. BOOL success = TRUE;
  1250. if (startEnd) inputLineStart = textLast;
  1251. else if (inputLineStart>=0 && k!=0)
  1252. { int lastNewline = inputLineStart;
  1253. while (lastNewline!=textLast && textBuffer[lastNewline]!='\n')
  1254. lastNewline = (lastNewline+1)&TEXT_MASK;
  1255. while (textLast!=lastNewline)
  1256. { int nn = (textLast-1)&TEXT_MASK;
  1257. if (textLast==caretChar) caretChar = nn;
  1258. if (textLast==icaretChar) icaretChar = nn;
  1259. if (textLast==selStartChar ||
  1260. textLast==selFirstChar ||
  1261. textLast==selEndChar) CancelSelection();
  1262. textLast = nn;
  1263. int c = textBuffer[textLast];
  1264. if (!UnTypeAhead(c)) success = FALSE;
  1265. if (c=='\n') lineLast = (lineLast-1)&LINE_MASK;
  1266. }
  1267. }
  1268. LineSizes();
  1269. CRect cr(0, LineY(oldCaretLine), clientWidth, clientHeight);
  1270. if (k==0) // No newlines inserted
  1271. { cr.left = caretX;
  1272. cr.bottom = LineY(caretLine)+LineDY(caretLine);
  1273. }
  1274. InvalidateRect(&cr);
  1275. UpdateWindow();
  1276. cwin_poll_window_manager();
  1277. return success;
  1278. }
  1279. void CMainWindow::cwin_caret_putchar(int ch)
  1280. {
  1281. if (hasFinished) return;
  1282. // If the caret happens to be at the end maybe I ought not to have got here
  1283. // anyway, but I will chain to the code that inserts things at the end of the
  1284. // text.
  1285. if (caretChar == textLast)
  1286. { cwin_putchar(ch);
  1287. return;
  1288. }
  1289. char buff[4];
  1290. buff[0] = ch;
  1291. // I beep if the insertion was impossible.
  1292. if (!InsertAtCaret(buff, 1)) ::MessageBeep(0xffffffff);
  1293. }
  1294. void CMainWindow::cwin_caret_replacechar(int ch)
  1295. {
  1296. if (hasFinished) return;
  1297. // If the caret happens to be at the end maybe I ought not to have got here
  1298. // anyway, but I will chain to the code that inserts things at the end of the
  1299. // text.
  1300. if (caretChar == textLast || ch == '\n' || textBuffer[caretChar] == '\n')
  1301. { cwin_putchar(ch);
  1302. return;
  1303. }
  1304. // Gosh this OUGHT to be easy!
  1305. textBuffer[caretChar] = ch;
  1306. if (caretChar == icaretChar)
  1307. { icaretX += caretFontWidths[ch];
  1308. icaretChar++;
  1309. }
  1310. CRect cr(caretX, LineY(caretLine),
  1311. clientWidth, LineY(caretLine)+LineDY(caretLine));
  1312. InvalidateRect(&cr);
  1313. caretX += caretFontWidths[ch];
  1314. caretChar++;
  1315. UpdateWindow();
  1316. cwin_ensure_screen(FALSE);
  1317. }
  1318. // When I have put enough characters on the screen my text buffer may become
  1319. // full. In which case I will discard the earliest stored line in it. Because
  1320. // the line will always be stored with at least a newline character present
  1321. // this guarantees to free up at least one byte in the buffer. I can also
  1322. // call this routine to discard the top line of the buffer when I need to
  1323. // recycle space in the record of lines (as distinct from characters).
  1324. //
  1325. // If I attempt to cancel part of the buffer by coming here but either the
  1326. // top line of the buffer is visible on the screen or part of the top line
  1327. // is included in a selection then I will pause until the user scrolls the
  1328. // window or does something to the selection that will mean that throwing
  1329. // away information will not discard anything still being looked at.
  1330. //
  1331. // Note that when I say here "Part of the first line is selected" I will
  1332. // mean that a non-empty part of the line is selected. Not having a
  1333. // selection in force is indicated by selStartChar==selEndChar, but the
  1334. // selection pointers can otherwise point anywhere at all in the buffer
  1335. // or indeed outside it.
  1336. void CMainWindow::WrapTextBuffer(BOOL waitForSelection)
  1337. {
  1338. selRootValid = FALSE;
  1339. BOOL usualTitle = TRUE;
  1340. if (waitForSelection)
  1341. { while ((lineVisible == lineFirst && caretChar != textLast &&
  1342. LineY(lineLast)+LineDY(lineLast)>=clientHeight) ||
  1343. (selStartLine == lineFirst &&
  1344. selStartChar != selEndChar))
  1345. { cwin_ensure_screen(FALSE);
  1346. if (usualTitle)
  1347. usualTitle = FALSE,
  1348. SetWindowText(
  1349. "Output pending: cancel selection/scroll down please");
  1350. cwin_poll_window_manager();
  1351. }
  1352. }
  1353. if (hasFinished) return;
  1354. if (!usualTitle) SetWindowText(mainTitle);
  1355. if (lineFirst==lineLast) OnClear(); // Drastic!
  1356. else
  1357. { lineFirst = (lineFirst+1)&LINE_MASK;
  1358. textFirst = lineBuffer[lineFirst].address;
  1359. }
  1360. }
  1361. void cwin_putchar(int c)
  1362. {
  1363. theApp.mainWindow->cwin_putchar(c);
  1364. inject_randomness(c);
  1365. inject_randomness((int)clock());
  1366. }
  1367. void CMainWindow::cwin_puts(const char *s)
  1368. {
  1369. if (hasFinished) return;
  1370. int ch;
  1371. // Here I should be careful and do just ONE call to Invalidate for all the
  1372. // changes that I make. But to start with it is MUCH easier to call putchar
  1373. // lots of times, so that is what I will do.
  1374. while ((ch=*s++)!=0) cwin_putchar(ch);
  1375. }
  1376. void cwin_puts(const char *s)
  1377. {
  1378. theApp.mainWindow->cwin_puts(s);
  1379. }
  1380. #ifndef MS_CDECL
  1381. #ifdef _MSC_VER
  1382. #define MS_CDECL __cdecl
  1383. #else
  1384. #define MS_CDECL
  1385. #endif
  1386. #endif
  1387. void MS_CDECL CMainWindow::cwin_printf(const char *s, ...)
  1388. {
  1389. if (hasFinished) return;
  1390. va_list a;
  1391. va_start(a, s);
  1392. cwin_vfprintf(s, a);
  1393. va_end(a);
  1394. }
  1395. void MS_CDECL cwin_printf(const char *s, ...)
  1396. {
  1397. if (hasFinished) return;
  1398. va_list a;
  1399. va_start(a, s);
  1400. theApp.mainWindow->cwin_vfprintf(s, a);
  1401. va_end(a);
  1402. }
  1403. // The versions of printf() that work with the window system use an internal
  1404. // buffer and print characters into that before sending them to the screen.
  1405. // It appears to be hard to check for overflow of this buffer, so the user
  1406. // will have to take care. A call to cwin_printf that generates more than
  1407. // MAX_PRINTF_OUTPUT characters can crash the system. Sorry. Maybe I
  1408. // should use the Watcom-specific _vbprintf() function here...
  1409. #define MAX_PRINTF_OUTPUT 256
  1410. void CMainWindow::cwin_vfprintf(const char *s, va_list a)
  1411. {
  1412. if (hasFinished) return;
  1413. char temp[MAX_PRINTF_OUTPUT];
  1414. #ifdef __WATCOMC__
  1415. _vbprintf(temp, MAX_PRINTF_OUTPUT, s, a);
  1416. // I put a zero byte at the end of the buffer to ensure that the string
  1417. // left there is properly terminated even if _vbprintf() wrote proper
  1418. // characters right up to the end.
  1419. temp[MAX_PRINTF_OUTPUT-1] = 0;
  1420. #else
  1421. vsprintf(temp, s, a);
  1422. #endif
  1423. cwin_puts(temp);
  1424. }
  1425. void cwin_vfprintf(const char *s, va_list a)
  1426. {
  1427. theApp.mainWindow->cwin_vfprintf(s, a);
  1428. }
  1429. void CMainWindow::cwin_ensure_screen(BOOL poll)
  1430. {
  1431. if (hasFinished) return;
  1432. // Here I need to do Invalidate operations, re-position the caret
  1433. // and adjust the position of scroll-bar thumbs.
  1434. if (caretVisible)
  1435. { OnVScroll(SB_FOR_CARET, 0, NULL);
  1436. OnHScroll(SB_FOR_CARET, 0, NULL);
  1437. }
  1438. // OnVScroll(SB_REFRESH_THUMB, 0, NULL);
  1439. HideCaret();
  1440. CPoint caretPos(caretX-xOffset, caretY);
  1441. SetCaretPos(caretPos);
  1442. ShowCaret();
  1443. if (poll) cwin_poll_window_manager();
  1444. }
  1445. void cwin_ensure_screen()
  1446. {
  1447. theApp.mainWindow->cwin_ensure_screen(TRUE);
  1448. }
  1449. // This will remove a character from the buffer at a location defined
  1450. // by the caret, which will in general not be at the end of the text.
  1451. void CMainWindow::cwin_caret_unputchar()
  1452. {
  1453. // When I try to remove a character from the screen I know that there is
  1454. // no selection active, since a DELETE when there was a selection deleted it
  1455. // as a block, and so did not reach this bit of code. So perhaps I can
  1456. // make this code work by creating a selected region containing the
  1457. // character (or prompt string) that I to be discarded and then do a
  1458. // DeleteSelection.
  1459. if (hasFinished) return;
  1460. // If it happens that I am at the end of the text I will call the other
  1461. // bit of character deletion code, so that in the bulk of this function I
  1462. // can know that I am in the general case (which tends to be an expensive one)
  1463. if (caretChar==textLast)
  1464. { cwin_unputchar();
  1465. return;
  1466. }
  1467. selRootValid = FALSE;
  1468. // If the caret is right at the start of the buffer I do not have
  1469. // anything to delete. This will, of course, include the case where the
  1470. // buffer is totally empty.
  1471. if (caretChar==textFirst) return;
  1472. // Set up the start of the selection at the position of the caret.
  1473. selFirstChar = selStartChar = selEndChar = caretChar;
  1474. selFirstX = selStartX = selEndX = caretX;
  1475. selFirstLine = selStartLine = selEndLine = caretLine;
  1476. // Now I need to step back a "character", where that could be a newline
  1477. // or a prompt string rather than any single simple character. I also
  1478. // ought to worry here about deletion of items in the text buffer that
  1479. // control formatting. All in all this is pretty dodgy, so I will cope with
  1480. // just the simple cases to start with.
  1481. int n;
  1482. if (lineBuffer[caretLine].address == caretChar) // deleting a newline?
  1483. { return;
  1484. // For now I am going to prevent deletion of or through a prompt or newline
  1485. n = (caretLine-1)&LINE_MASK;
  1486. caretLine=n;
  1487. caretX = lineBuffer[n].width;
  1488. n = lineBuffer[n].address;
  1489. while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
  1490. }
  1491. else
  1492. { n = (caretChar-1) & TEXT_MASK;
  1493. // I produce just an APPROXIMATION to the desired caretX here, but want that
  1494. // so that I can limit the amount of re-painting that happens.
  1495. int w = windowFonts.HCourier.across['X'];
  1496. if ((textBuffer[n]&0xff)==CH_ENDPROMPT) // delete whole of a prompt
  1497. {
  1498. return; // do not delete prompt
  1499. do
  1500. { n = (n-1)&TEXT_MASK;
  1501. caretX -= w;
  1502. } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT);
  1503. }
  1504. else caretX -= w;
  1505. }
  1506. caretChar = n;
  1507. if (caretLine == lineLast)
  1508. { icaretChar = caretChar;
  1509. icaretLine = caretLine;
  1510. icaretX = caretX;
  1511. }
  1512. selStartChar = n;
  1513. selStartLine = caretLine;
  1514. selStartX = caretX;
  1515. DeleteSelection();
  1516. }
  1517. // Now a version of unputchar that deletes characters from the end of the
  1518. // text buffer (and the caret may or may not be there).
  1519. void CMainWindow::cwin_unputchar()
  1520. {
  1521. if (hasFinished) return;
  1522. selRootValid = FALSE;
  1523. int n;
  1524. if (textFirst == textLast) return; // utterly empty buffer
  1525. if (lineBuffer[lineLast].address == textLast) // deleting a newline?
  1526. { return;
  1527. n = (lineLast-1)&LINE_MASK;
  1528. endX = lineBuffer[n].width;
  1529. if (caretLine==lineLast) caretLine=n, caretX=endX;
  1530. if (icaretLine==lineLast) icaretLine=n, icaretX=endX;
  1531. lineLast=n;
  1532. n = lineBuffer[n].address;
  1533. while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
  1534. if (textLast == caretChar) caretChar = n;
  1535. if (textLast == icaretChar) icaretChar = n;
  1536. if (textLast == inputLineStart) inputLineStart = n;
  1537. textLast = n;
  1538. }
  1539. else
  1540. { n = (textLast-1) & TEXT_MASK;
  1541. int w = windowFonts.HCourier.across['X'];
  1542. if ((textBuffer[n]&0xff)==CH_ENDPROMPT) // delete whole of a prompt
  1543. { return;
  1544. do
  1545. { n = (n-1)&TEXT_MASK;
  1546. endX -= w;
  1547. } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT);
  1548. }
  1549. else endX -= w;
  1550. if (textLast == caretChar) caretChar = n;
  1551. if (textLast == icaretChar) icaretChar = n;
  1552. if (textLast == inputLineStart) inputLineStart = n;
  1553. textLast = n;
  1554. // The amount of invalidation done here runs across to the end of the
  1555. // last line, and that ought to get rid of any residues of the old
  1556. // caret. I use an APPROXIMATION to the new value of endX, but hope that
  1557. // that gets corrected during the re-painting.
  1558. CRect cr(endX-xOffset, LineY(lineLast),
  1559. clientWidth, LineY(lineLast)+LineDY(lineLast));
  1560. InvalidateRect(&cr); // Should I hide the caret here?
  1561. }
  1562. UpdateWindow();
  1563. HideCaret();
  1564. CPoint caretPos(caretX-xOffset, caretY);
  1565. SetCaretPos(caretPos);
  1566. ShowCaret();
  1567. }
  1568. int CMainWindow::cwin_getchar()
  1569. {
  1570. if (hasFinished) return EOF;
  1571. int ch;
  1572. if (inputP >= 0)
  1573. { ch = inputBuffer[inputP++];
  1574. if (ch == '\n') inputP = -1; // This buffer now all used up.
  1575. return ch;
  1576. }
  1577. cwin_putchar(CH_PROMPT);
  1578. cwin_puts(cwin_prompt_string);
  1579. if (spool_file != NULL) fprintf(spool_file, "%s", cwin_prompt_string);
  1580. cwin_putchar(CH_ENDPROMPT);
  1581. if (hasFinished) return EOF;
  1582. inputLineStart = textLast; // just beyond the prompt
  1583. // Next I move characters across from the type-ahead buffer into the main
  1584. // text buffer, echoing them as I go (and handling DELETE chars too). I
  1585. // stop when I find a newline. Perhaps some people would like if if some
  1586. // other characters would terminate an input line, but for the moment I am
  1587. // sticking to just newline.... except that any keyboard or menu activity
  1588. // that raises an "exception" will stop input for me rather abruptly.
  1589. for (;;)
  1590. { cwin_ensure_screen(FALSE);
  1591. while (typeAheadP1 == typeAheadP2 &&
  1592. clipboardInput == NULL)
  1593. { cwin_poll_window_manager();
  1594. if (hasFinished || exception_pending()) return EOF;
  1595. }
  1596. if (clipboardInput != NULL)
  1597. { ch = *clipboardInputP++;
  1598. // When input is coming from the clipboard to the very end of the input
  1599. // text I will ignore copied prompt strings. Note that this is different
  1600. // from the situation with PASTE inserts into the middle of the text.
  1601. // A curious side-effect here is that if a regular TEXT clipboard file is
  1602. // pasted in and it happens to contain these funny character codes...
  1603. while ((ch&0xff) == CH_PROMPT)
  1604. { while ((ch = *clipboardInputP++) != 0 &&
  1605. (ch&0xff) != CH_ENDPROMPT);
  1606. if (ch == 0) break;
  1607. ch = *clipboardInputP++;
  1608. }
  1609. if (ch == '\r') continue;
  1610. if (ch == 0)
  1611. { free(clipboardInput);
  1612. clipboardInput = NULL;
  1613. continue;
  1614. }
  1615. }
  1616. else
  1617. { ch = typeAheadBuffer[typeAheadP1];
  1618. typeAheadP1 = (typeAheadP1 + 1) & typeAheadBufferMask;
  1619. }
  1620. if (ch == 0x7f) cwin_unputchar();
  1621. else cwin_putchar(ch);
  1622. if (ch == '\n' || hasFinished) break;
  1623. }
  1624. if (hasFinished) return EOF;
  1625. // By the time I exit the above loop there must be at least one character
  1626. // in the line that has just been read, even if it is only the terminating
  1627. // '\n'.
  1628. inputP = 0;
  1629. // When I copy stuff from the screen into the input buffer I discard any
  1630. // prompts. They might be there if the line of input was created in part by
  1631. // pasting material into the middle of a partly-typed line.
  1632. BOOL inPrompt = FALSE;
  1633. for (;;)
  1634. { ch = textBuffer[inputLineStart];
  1635. inputLineStart = (inputLineStart+1)&TEXT_MASK;
  1636. // Here I quietly truncate very very long input lines. For my first attempt
  1637. // I have inputBufferSize set at 2048.
  1638. if ((ch&0xff) == CH_PROMPT) inPrompt = TRUE;
  1639. if (inputP<inputBufferSize && !inPrompt) inputBuffer[inputP++] = ch;
  1640. if ((ch&0xff) == CH_ENDPROMPT) inPrompt = FALSE;
  1641. else if (ch == '\n') break;
  1642. }
  1643. inputBuffer[inputBufferSize-1] = '\n';
  1644. // Now I have the next line of user input in inputBuffer, running from 0
  1645. // up to the first '\n' (and I know there is a '\n' in there somewhere).
  1646. // I want to copy it away into a "saved input lines" buffer, so that they
  1647. // can be retrieved later by a DOSKEY-like protocol. Note that provided
  1648. // I keep the size of the save buffer larger than that of the input buffer
  1649. // the copying operation here will not cause embarassing overflow!
  1650. currentInputLine = -1;
  1651. savedLines[savedP2++] = savedLast;
  1652. if (savedP2 == MAX_SAVED_LINES) savedP2 = 0;
  1653. if (savedP2 == savedP1)
  1654. { savedP1++;
  1655. if (savedP1 == MAX_SAVED_LINES) savedP1 = 0;
  1656. savedFirst = savedLines[savedP1];
  1657. }
  1658. inputP = 0;
  1659. for (;;)
  1660. { ch = inputBuffer[inputP++];
  1661. savedChars[savedLast++] = ch;
  1662. if (savedLast == MAX_SAVED_CHARS) savedLast = 0;
  1663. if (savedLast == savedFirst)
  1664. { savedP1++;
  1665. if (savedP1 == MAX_SAVED_LINES) savedP1 = 0;
  1666. savedFirst = savedLines[savedP1];
  1667. }
  1668. if (ch == '\n') break;
  1669. }
  1670. inputP = 0;
  1671. ch = inputBuffer[inputP++];
  1672. if (ch == '\n') inputP = -1; // This buffer now all used up.
  1673. cwin_ensure_screen(FALSE);
  1674. return ch;
  1675. }
  1676. int cwin_getchar()
  1677. {
  1678. return theApp.mainWindow->cwin_getchar();
  1679. }
  1680. int CMainWindow::cwin_getchar_nowait()
  1681. {
  1682. if (hasFinished) return EOF;
  1683. int ch;
  1684. if (inputP >= 0)
  1685. { ch = inputBuffer[inputP++];
  1686. if (ch == '\n') inputP = -1; // This buffer now all used up.
  1687. return ch;
  1688. }
  1689. cwin_putchar(CH_PROMPT);
  1690. cwin_puts(cwin_prompt_string);
  1691. if (spool_file != NULL) fprintf(spool_file, "%s", cwin_prompt_string);
  1692. cwin_putchar(CH_ENDPROMPT);
  1693. if (hasFinished) return EOF;
  1694. inputLineStart = textLast; // just beyond the prompt
  1695. // Next I move characters across from the type-ahead buffer into the main
  1696. // text buffer, echoing them as I go (and handling DELETE chars too). I
  1697. // stop when I find a newline.
  1698. for (;;)
  1699. { cwin_ensure_screen(FALSE);
  1700. if (typeAheadP1 == typeAheadP2 &&
  1701. clipboardInput == NULL) return EOF;
  1702. if (clipboardInput != NULL)
  1703. { ch = *clipboardInputP++;
  1704. while ((ch&0xff) == CH_PROMPT)
  1705. { while ((ch = *clipboardInputP++) != 0 &&
  1706. (ch&0xff) != CH_ENDPROMPT);
  1707. if (ch == 0) break;
  1708. ch = *clipboardInputP++;
  1709. }
  1710. if (ch == '\r') continue;
  1711. if (ch == 0)
  1712. { free(clipboardInput);
  1713. clipboardInput = NULL;
  1714. continue;
  1715. }
  1716. }
  1717. else
  1718. { ch = typeAheadBuffer[typeAheadP1];
  1719. typeAheadP1 = (typeAheadP1 + 1) & typeAheadBufferMask;
  1720. }
  1721. if (ch == 0x7f) cwin_unputchar();
  1722. else cwin_putchar(ch);
  1723. if (ch == '\n' || hasFinished) break;
  1724. }
  1725. if (hasFinished) return EOF;
  1726. // By the time I exit the above loop there must be at least one character
  1727. // in the line that has just been read, even if it is only the terminating
  1728. // '\n'.
  1729. inputP = 0;
  1730. BOOL inPrompt = FALSE;
  1731. for (;;)
  1732. { ch = textBuffer[inputLineStart];
  1733. inputLineStart = (inputLineStart+1)&TEXT_MASK;
  1734. // Here I quietly truncate very very long input lines. For my first attempt
  1735. // I have inputBufferSize set at 2048.
  1736. if ((ch&0xff) == CH_PROMPT) inPrompt = TRUE;
  1737. if (inputP<inputBufferSize && !inPrompt) inputBuffer[inputP++] = ch;
  1738. if ((ch&0xff) == CH_ENDPROMPT) inPrompt = FALSE;
  1739. else if (ch == '\n') break;
  1740. }
  1741. inputBuffer[inputBufferSize-1] = '\n';
  1742. inputP = 0;
  1743. ch = inputBuffer[inputP++];
  1744. if (ch == '\n') inputP = -1; // This buffer now all used up.
  1745. return ch;
  1746. }
  1747. int cwin_getchar_nowait()
  1748. {
  1749. return theApp.mainWindow->cwin_getchar_nowait();
  1750. }
  1751. void CMainWindow::cwin_discard_input()
  1752. {
  1753. typeAheadP1 = typeAheadP2 = 0;
  1754. if (clipboardInput) free(clipboardInput);
  1755. clipboardInput = 0;
  1756. }
  1757. void cwin_discard_input()
  1758. {
  1759. theApp.mainWindow->cwin_discard_input();
  1760. }
  1761. void CMainWindow::cwin_set_prompt(const char *s)
  1762. {
  1763. strncpy(cwin_prompt_string, s, 31);
  1764. cwin_prompt_string[31] = 0;
  1765. }
  1766. void cwin_set_prompt(const char *s)
  1767. {
  1768. theApp.mainWindow->cwin_set_prompt(s);
  1769. }
  1770. // The READ command on the menu will work by forcing the caret to the
  1771. // end of the input buffer and simulating some typed in text which
  1772. // contains a file-name. I will make a start at pretending to make it
  1773. // configurable here. But there seem to be LOTS of things that need to be
  1774. // changed to match the system that is using the window manager code...
  1775. // I hope that eventually I will provide configuration calls so that the
  1776. // grim detail here gets set from "user" code. The basic idea is that
  1777. // readFileCommand holds a string to be generated, with a marker (%) somewhere
  1778. // where the file-name should go. I need options to control processing
  1779. // of the file-name to get around reader escape conventions etc. I also need
  1780. // to tell the file selection dialog what sorts of files to look for...
  1781. #define VERBATIM_FILENAME 0
  1782. #define DOUBLE_BACKSLASH 1
  1783. #define DOUBLE_DOUBLEQUOTE 2
  1784. #ifdef COMMON
  1785. static char *readFileCommand = "(load \"%\")";
  1786. static int fileOptions = DOUBLE_BACKSLASH;
  1787. static char *defaultExtension = "lsp";
  1788. static char *defaultFile = "*.lsp";
  1789. static char *fileNameFilter =
  1790. "Lisp Files (*.lsp)|*.lsp|All Files (*.*)|*.*||\0";
  1791. #else
  1792. static char *readFileCommand = "in \"%\";";
  1793. static int fileOptions = DOUBLE_DOUBLEQUOTE;
  1794. static char *defaultExtension = "red";
  1795. static char *defaultFile = "*.RED;*.TST";
  1796. static char *fileNameFilter =
  1797. "Reduce (*.red/*.tst)|*.RED;*.TST|Lisp Files (*.lsp)|*.LSP|All Files (*.*)|*.*||\0";
  1798. #endif
  1799. void CMainWindow::OnRead()
  1800. {
  1801. OnEnd();
  1802. pageLine = caretLine;
  1803. CFileDialog FDialog(TRUE, defaultExtension,
  1804. defaultFile, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
  1805. fileNameFilter, NULL);
  1806. FDialog.m_ofn.lpstrTitle = "Read File";
  1807. if (FDialog.DoModal() != IDOK)
  1808. { cwin_ensure_screen(FALSE); // needed to re-position caret, it seems.
  1809. return;
  1810. }
  1811. CString p = FDialog.GetPathName();
  1812. int nameLength = p.GetLength();
  1813. char *q = readFileCommand;
  1814. int i = strlen(q);
  1815. while (i-- != 0)
  1816. { int ch = q[i];
  1817. if (ch == '%')
  1818. { int j = nameLength;
  1819. while (j-- != 0)
  1820. { int ch1 = p[j];
  1821. switch (fileOptions)
  1822. {
  1823. case DOUBLE_BACKSLASH:
  1824. if (ch1 == '\\') UnTypeAhead(ch1);
  1825. break;
  1826. case DOUBLE_DOUBLEQUOTE:
  1827. if (ch1 == '"') UnTypeAhead(ch1);
  1828. break;
  1829. default:break;
  1830. }
  1831. UnTypeAhead(ch1);
  1832. }
  1833. }
  1834. else UnTypeAhead(ch);
  1835. }
  1836. cwin_ensure_screen(FALSE);
  1837. }
  1838. #ifndef COMMON
  1839. static char **library_name_p = NULL;
  1840. static int library_name_n = 0;
  1841. static char *library_name[] =
  1842. {
  1843. "algint",
  1844. "applysym",
  1845. "arnum",
  1846. "assist",
  1847. "avector",
  1848. "boolean",
  1849. "cali",
  1850. "camal",
  1851. "changevr",
  1852. "compact",
  1853. "complex",
  1854. "crack",
  1855. "cvit",
  1856. "decompos",
  1857. "defint",
  1858. "desir",
  1859. "dfpart",
  1860. "dummy",
  1861. "elem",
  1862. "excalc",
  1863. "fide",
  1864. "fps",
  1865. "gentran",
  1866. "gnuplot",
  1867. "groebner",
  1868. "ideals",
  1869. "ineq",
  1870. "invbase",
  1871. "laplace",
  1872. "lie",
  1873. "linalg",
  1874. "modsr",
  1875. "ncpoly",
  1876. "normform",
  1877. "numeric",
  1878. "odesolve",
  1879. "orthovec",
  1880. "physop",
  1881. "pmrules",
  1882. "randpoly",
  1883. "reacteqn",
  1884. "residue",
  1885. "rlfi",
  1886. "rsolve",
  1887. "scope",
  1888. "sets",
  1889. "spde",
  1890. "specfn",
  1891. "symmetry",
  1892. "taylor",
  1893. "tps",
  1894. "tri",
  1895. "trigsimp",
  1896. "wu",
  1897. "xcolor",
  1898. "xideal",
  1899. "zeilberg",
  1900. "ztrans"
  1901. };
  1902. void CMainWindow::OnLoadLibrary(UINT a)
  1903. {
  1904. OnEnd();
  1905. pageLine = caretLine;
  1906. char q[100];
  1907. int i;
  1908. if (library_name_p != NULL)
  1909. { if (a-IDM_FIRSTLOAD >= library_name_n) return;
  1910. sprintf(q, "load_package %s;\n",
  1911. library_name_p[a - IDM_FIRSTLOAD]);
  1912. }
  1913. else
  1914. { sprintf(q, "load_package %s;\n", library_name[a - IDM_FIRSTLOAD]);
  1915. }
  1916. i = strlen(q);
  1917. while (i-- != 0)
  1918. { int ch = q[i];
  1919. UnTypeAhead(ch);
  1920. }
  1921. cwin_ensure_screen(FALSE);
  1922. }
  1923. static char **switch_name_p = NULL;
  1924. static int switch_name_n = 0;
  1925. static struct { char *name; int status; } switch_name[] =
  1926. {
  1927. {"algint", 0},
  1928. {"adjprec", 0},
  1929. {"allbranch", 0},
  1930. {"allfac", 1},
  1931. {"arbvars", 0},
  1932. {"asterisk", 1},
  1933. {"backtrace", 0},
  1934. {"balanced_mod", 0},
  1935. {"bfspace", 0},
  1936. {"combineexpt", 0},
  1937. {"combinelogs", 0},
  1938. {"comp", 0},
  1939. {"complex", 0},
  1940. {"compxroots", 0},
  1941. {"cramer", 0},
  1942. {"cref", 0},
  1943. {"defn", 0},
  1944. {"demo", 0},
  1945. {"dfprint", 0},
  1946. {"div", 0},
  1947. {"echo", 0},
  1948. {"errcont", 0},
  1949. {"evallhseqp", 0},
  1950. {"exp", 1},
  1951. {"expandexpt", 1},
  1952. {"expandlogs", 0},
  1953. {"ezgcd", 0},
  1954. {"factor", 0},
  1955. {"fastfor", 0},
  1956. {"force", 0},
  1957. {"fort", 0},
  1958. {"fortupper", 0},
  1959. {"fullprec", 0},
  1960. {"fullprecision", 0},
  1961. {"fullroots", 0},
  1962. {"gc", 0},
  1963. {"gcd", 0},
  1964. {"heugcd", 0},
  1965. {"horner", 0},
  1966. {"ifactor", 0},
  1967. {"int", 0},
  1968. {"intstr", 0},
  1969. {"lcm", 1},
  1970. {"lessspace", 0},
  1971. {"limitedfactors", 0},
  1972. {"list", 0},
  1973. {"listargs", 0},
  1974. {"lower", 1},
  1975. {"mcd", 1},
  1976. {"modular", 0},
  1977. {"msg", 1},
  1978. {"multiplicities", 0},
  1979. {"nat", 1},
  1980. {"nero", 0},
  1981. {"noarg", 1},
  1982. {"noconvert", 0},
  1983. {"nonlnr", 0},
  1984. {"nosplit", 1},
  1985. {"numval", 1},
  1986. {"output", 1},
  1987. {"period", 1},
  1988. {"pgwd", 0},
  1989. {"plap", 0},
  1990. {"precise", 1},
  1991. {"pret", 0},
  1992. {"pri", 1},
  1993. {"pwrds", 1},
  1994. {"quotenewnam", 1},
  1995. {"raise", 0},
  1996. {"rat", 0},
  1997. {"ratarg", 0},
  1998. {"rational", 0},
  1999. {"rationalize", 0},
  2000. {"ratpri", 1},
  2001. {"reduced", 0},
  2002. {"revpri", 0},
  2003. {"rlisp88", 0},
  2004. {"rootmsg", 0},
  2005. {"roundall", 1},
  2006. {"roundbf", 0},
  2007. {"rounded", 0},
  2008. {"savestructr", 0},
  2009. {"solvesingular", 0},
  2010. {"time", 0},
  2011. {"trallfac", 0},
  2012. {"trfac", 0},
  2013. {"trint", 0},
  2014. {"trroot", 0}
  2015. };
  2016. void CMainWindow::OnSwitch(UINT a)
  2017. {
  2018. OnEnd();
  2019. pageLine = caretLine;
  2020. char q[100];
  2021. int i;
  2022. if (switch_name_p != NULL)
  2023. { int n = a - IDS_FIRSTSWITCH;
  2024. if (n >= switch_name_n) return;
  2025. i = switch_name_p[n][0] == 'y';
  2026. switch_name_p[n][0] ^= ('y' ^ 'n');
  2027. // Note well - if the user types "on xxx" or "off xxx" directly rather than
  2028. // by using this mechanism then the checked or otherwise status of the menu
  2029. // items will get out of step. Tough luck. If you use the menu to set or clear
  2030. // a switch the switch will certainly be brought into line with what the
  2031. // menu information indicates.
  2032. GetMenu()->CheckMenuItem(a, MF_BYCOMMAND |
  2033. (i ? MF_UNCHECKED : MF_CHECKED));
  2034. DrawMenuBar();
  2035. sprintf(q, "%s %s;\n",
  2036. i ? "off" : "on",
  2037. 1+switch_name_p[n]);
  2038. }
  2039. else
  2040. { i = switch_name[a - IDS_FIRSTSWITCH].status;
  2041. switch_name[a - IDS_FIRSTSWITCH].status = !i;
  2042. // Note well - if the user types "on xxx" or "off xxx" directly rather than
  2043. // by using this mechanism then the checked or otherwise status of the menu
  2044. // items will get out of step. Tough luck. If you use the menu to set or clear
  2045. // a switch the switch will certainly be brought into line with what the
  2046. // menu information indicates.
  2047. GetMenu()->CheckMenuItem(a, MF_BYCOMMAND |
  2048. (i ? MF_UNCHECKED : MF_CHECKED));
  2049. DrawMenuBar();
  2050. sprintf(q, "%s %s;\n",
  2051. i ? "off" : "on",
  2052. switch_name[a - IDS_FIRSTSWITCH].name);
  2053. }
  2054. i = strlen(q);
  2055. while (i-- != 0)
  2056. { int ch = q[i];
  2057. UnTypeAhead(ch);
  2058. }
  2059. cwin_ensure_screen(FALSE);
  2060. }
  2061. #endif
  2062. void CMainWindow::OnExit()
  2063. {
  2064. pageLine = caretLine;
  2065. DestroyWindow();
  2066. }
  2067. int cwin_interrupt_pending = 0;
  2068. void CMainWindow::OnInterrupt()
  2069. {
  2070. pageLine = caretLine;
  2071. cwin_interrupt_pending = 1;
  2072. cwin_ensure_screen(FALSE);
  2073. }
  2074. void CMainWindow::OnBacktrace()
  2075. {
  2076. pageLine = caretLine;
  2077. cwin_interrupt_pending = 3;
  2078. cwin_ensure_screen(FALSE);
  2079. }
  2080. void CMainWindow::OnPageMode()
  2081. {
  2082. pageLine = caretLine;
  2083. pageMode = !pageMode;
  2084. GetMenu()->CheckMenuItem(IDM_PAGEMODE,
  2085. MF_BYCOMMAND | (pageMode ? MF_CHECKED : MF_UNCHECKED));
  2086. DrawMenuBar();
  2087. cwin_ensure_screen(FALSE);
  2088. }
  2089. #ifdef GRAPHICS_WINDOW
  2090. void CMainWindow::OnGraphics()
  2091. {
  2092. graphicsWindow->ShowWindow(graphicsShown ? SW_HIDE : SW_SHOW);
  2093. if (graphicsShown) graphicsWindow->viewpointWindow.ShowWindow(SW_HIDE);
  2094. graphicsShown = !graphicsShown;
  2095. if (graphicsShown) graphicsWindow->Invalidate();
  2096. GetMenu()->CheckMenuItem(IDM_GRAPHICS,
  2097. MF_BYCOMMAND | (graphicsShown ? MF_CHECKED : MF_UNCHECKED));
  2098. DrawMenuBar();
  2099. cwin_ensure_screen(FALSE);
  2100. }
  2101. void CMainWindow::OnGraphics1(UINT a, LONG b)
  2102. {
  2103. OnGraphics();
  2104. }
  2105. #endif
  2106. void CMainWindow::ReWriteTitleText()
  2107. {
  2108. if (hasFinished) return;
  2109. CWindowDC dc(this);
  2110. // It is not (at present) clear to me how to decide how wide to make
  2111. // the title. What follows is some sort of a guess.
  2112. CRect clientArea;
  2113. GetClientRect(&clientArea);
  2114. int wTitle = clientArea.Width() -
  2115. 3*::GetSystemMetrics(SM_CXVSCROLL);
  2116. int wLeft = dc.GetTextExtent(cLeft, strlen(cLeft)).cx;
  2117. int wMid = dc.GetTextExtent(cMid, strlen(cMid)).cx;
  2118. int wRight = dc.GetTextExtent(cRight, strlen(cRight)).cx;
  2119. // For the calculations about padding that I use here to work the font used
  2120. // in the title bar had better not do any kerning and overhang-effects must
  2121. // not interfere. I find I have all sorts of horrid problems if I try to
  2122. // use regular blank characters for padding, but '\xa0' displays as blank
  2123. // space and is better behaved. It also seems (???) that at least under
  2124. // Windows 3.1 there is no great joy in trying to use caption strings that
  2125. // are longer than 78 characters...
  2126. #define PADDING_CHAR '\xa0'
  2127. char strSp[4]; strSp[0] = PADDING_CHAR;
  2128. int wSp = dc.GetTextExtent(strSp, 1).cx;
  2129. int cw = wTitle / wSp;
  2130. // I first measure things on the supposition that I would allow up to
  2131. // 90 characters in the title. After balancing it a bit I cut that
  2132. // down to the 78 that Windows 3.1 seems to be prepared to tolerate.
  2133. if (cw > 90) wTitle = 90*wSp;
  2134. int pad = (wTitle - wMid)/2;
  2135. char *l = cLeft, *r = cRight;
  2136. for (;;)
  2137. { int padLeft = (pad - wLeft) / wSp;
  2138. int padRight = (pad - wRight) / wSp;
  2139. int excess = strlen(cLeft) + padLeft + strlen(cMid) +
  2140. padRight + strlen(cRight) - 78;
  2141. if (excess > 0)
  2142. { if (excess & 1)
  2143. { if (padLeft > padRight) padLeft--; else padRight--;
  2144. excess--;
  2145. }
  2146. excess /= 2;
  2147. padLeft -= excess;
  2148. padRight -= excess;
  2149. }
  2150. #ifdef DISCARD_LEFT_IF_TITLE_OVERFULL
  2151. if (padLeft <= 0 && padRight <= 0)
  2152. { strcpy(mainTitle, cMid); // Abandon both right & left items
  2153. break;
  2154. }
  2155. #else
  2156. // Here even though there is no room to display the whole text I wanted
  2157. // on the title bar (maybe the window had been iconized?) I will display
  2158. // the left & mid parts with a single blank between.
  2159. if (padLeft <= 0 && padRight <= 0)
  2160. { sprintf(mainTitle, "%s%c%s", cLeft, PADDING_CHAR, cMid);
  2161. break;
  2162. }
  2163. #endif
  2164. else
  2165. {
  2166. #ifdef DISCARD_LEFT_IF_TITLE_OVERFULL
  2167. if (padLeft <= 0 && wLeft != 0)
  2168. { l = ""; // Abandon left item & re-try
  2169. wLeft = 0;
  2170. continue;
  2171. }
  2172. #endif
  2173. if (padRight <= 0 && wRight != 0) // Abandon right item & re-try
  2174. { r = "";
  2175. wRight = 0;
  2176. continue;
  2177. }
  2178. char *p = mainTitle;
  2179. while (*l != 0) *p++ = *l++;
  2180. for (int i=0; i<padLeft; i++) *p++ = PADDING_CHAR;
  2181. l = cMid;
  2182. while (*l != 0) *p++ = *l++;
  2183. for (i=0; i<padRight; i++) *p++ = PADDING_CHAR;
  2184. while (*r != 0) *p++ = *r++;
  2185. *p = 0;
  2186. break;
  2187. }
  2188. }
  2189. SetWindowText(mainTitle);
  2190. }
  2191. void CMainWindow::cwin_report_left(const char *msg)
  2192. {
  2193. // I will take this opportunity to make the "To-File" menu item track
  2194. // whether there really is a spool file active. This only effects how
  2195. // the menu gets displayed, not what it does...
  2196. if (complete)
  2197. { GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND, IDM_TOFILE,
  2198. spool_file==NULL ? "&To File ..." :
  2199. "&Terminate log");
  2200. DrawMenuBar();
  2201. }
  2202. if (msg == NULL)
  2203. { leftSetByUser = FALSE;
  2204. return; // Date and time of day will appear
  2205. }
  2206. strncpy(cLeft, msg, 31); cLeft[31] = 0;
  2207. ReWriteTitleText();
  2208. leftSetByUser = TRUE;
  2209. }
  2210. void cwin_report_left(const char *s)
  2211. {
  2212. theApp.mainWindow->cwin_report_left(s);
  2213. }
  2214. void CMainWindow::cwin_report_mid(const char *msg)
  2215. {
  2216. if (msg == NULL) msg = programName;
  2217. strncpy(cMid, msg, 31); cLeft[31] = 0;
  2218. ReWriteTitleText();
  2219. }
  2220. void cwin_report_mid(const char *s)
  2221. {
  2222. theApp.mainWindow->cwin_report_mid(s);
  2223. }
  2224. void CMainWindow::cwin_report_right(const char *msg)
  2225. {
  2226. if (msg == NULL) msg = "";
  2227. strncpy(cRight, msg, 31); cRight[31] = 0;
  2228. ReWriteTitleText();
  2229. }
  2230. void cwin_report_right(const char *s)
  2231. {
  2232. theApp.mainWindow->cwin_report_right(s);
  2233. }
  2234. void CMainWindow::cwin_display_date()
  2235. {
  2236. char dateBuffer[20];
  2237. time_t t0 = time(NULL);
  2238. char *m = ctime(&t0);
  2239. #ifdef OLD_AND_USES_UNIVERSAL_TIME_WHICH_IS_SILLY
  2240. sprintf(dateBuffer, "%02d-%.3s-%02d, %02d:%02d:%02d",
  2241. titleUpdateTime.wDay,
  2242. "xxxJanFebMarAprMayJunJulAugSepOctNovDec"+3*titleUpdateTime.wMonth,
  2243. titleUpdateTime.wYear%100,
  2244. titleUpdateTime.wHour, titleUpdateTime.wMinute,
  2245. titleUpdateTime.wSecond);
  2246. #else
  2247. sprintf(dateBuffer, "%.2s-%.3s-%.2s, %.8s",
  2248. m+8, m+4, m+22, m+11);
  2249. #endif
  2250. cwin_report_left(dateBuffer);
  2251. leftSetByUser = FALSE;
  2252. }
  2253. void cwin_menus(char **packages, char **switches)
  2254. {
  2255. theApp.mainWindow->cwin_menus(packages, switches);
  2256. }
  2257. void CMainWindow::cwin_menus(char **packages, char **switches)
  2258. {
  2259. CMenu *main = GetMenu(); // main menu bar for this window
  2260. if (packages != NULL && *packages != NULL)
  2261. { library_name_p = packages;
  2262. main->DeleteMenu(4, MF_BYPOSITION);
  2263. int n = IDM_FIRSTLOAD;
  2264. CMenu mload;
  2265. mload.CreatePopupMenu();
  2266. int firstletter = 'a';
  2267. int lastletter = 'a', nextletter;
  2268. int count = 0, nextcount;
  2269. char **p = packages;
  2270. library_name_n = 0;
  2271. while (*p++ != NULL) library_name_n++;
  2272. p = packages;
  2273. while (*p && **p == lastletter) count++, p++;
  2274. char **p1 = p;
  2275. while (*packages)
  2276. { for (;;)
  2277. { nextcount = 0;
  2278. nextletter = lastletter + 1;
  2279. while (*p && **p == nextletter) nextcount++, p++;
  2280. if (count + nextcount > 20) break;
  2281. lastletter = nextletter;
  2282. count += nextcount;
  2283. p1 = p;
  2284. if (lastletter == 'z') break;
  2285. }
  2286. char subname[8];
  2287. if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
  2288. else sprintf(subname, "%c-%c", firstletter, lastletter);
  2289. CMenu sub1;
  2290. sub1.CreatePopupMenu();
  2291. while (packages != p1)
  2292. sub1.AppendMenu(MF_STRING, n++, *packages++);
  2293. mload.AppendMenu(MF_STRING | MF_POPUP, (UINT)sub1.Detach(), subname);
  2294. firstletter = lastletter = nextletter;
  2295. count = nextcount;
  2296. p1 = p;
  2297. }
  2298. main->InsertMenu(4, MF_BYPOSITION | MF_POPUP | MF_STRING,
  2299. (UINT)mload.Detach(), "&Load");
  2300. }
  2301. // Now do roughly the same with switches
  2302. if (switches != NULL && *switches != NULL)
  2303. { switch_name_p = switches;
  2304. main->DeleteMenu(5, MF_BYPOSITION);
  2305. int n = IDS_FIRSTSWITCH;
  2306. CMenu mload;
  2307. mload.CreatePopupMenu();
  2308. int firstletter = 'a';
  2309. int lastletter = 'a', nextletter;
  2310. int count = 0, nextcount;
  2311. char **p = switches;
  2312. switch_name_n = 0;
  2313. while (*p++ != NULL) switch_name_n++;
  2314. p = switches;
  2315. while (*p && (*p)[1] == lastletter) count++, p++;
  2316. char **p1 = p;
  2317. while (*switches)
  2318. { for (;;)
  2319. { nextcount = 0;
  2320. nextletter = lastletter + 1;
  2321. while (*p && (*p)[1] == nextletter) nextcount++, p++;
  2322. if (count + nextcount > 20) break;
  2323. lastletter = nextletter;
  2324. count += nextcount;
  2325. p1 = p;
  2326. if (lastletter == 'z') break;
  2327. }
  2328. char subname[8];
  2329. if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
  2330. else sprintf(subname, "%c-%c", firstletter, lastletter);
  2331. CMenu sub1;
  2332. sub1.CreatePopupMenu();
  2333. while (switches != p1)
  2334. { sub1.AppendMenu(MF_STRING, n, 1+*switches);
  2335. sub1.CheckMenuItem(n, MF_BYCOMMAND |
  2336. (**switches=='y' ? MF_CHECKED : MF_UNCHECKED));
  2337. n++;
  2338. switches++;
  2339. }
  2340. mload.AppendMenu(MF_STRING | MF_POPUP, (UINT)sub1.Detach(), subname);
  2341. firstletter = lastletter = nextletter;
  2342. count = nextcount;
  2343. p1 = p;
  2344. }
  2345. main->InsertMenu(5, MF_BYPOSITION | MF_POPUP | MF_STRING,
  2346. (UINT)mload.Detach(), "&Switch");
  2347. }
  2348. DrawMenuBar();
  2349. }
  2350. // When the user types in a key all that happens (to start with) is that
  2351. // I store it in a buffer. If the buffer gets to be full I beep and
  2352. // ignore any further characters.
  2353. #define Ctrl(x) ((x) & 0x1f)
  2354. void CMainWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags)
  2355. {
  2356. // I turn the ENTER key into a newline character (and consequently ^M will
  2357. // also go that way). I also discard 0x7f (for the delete/erase key) and
  2358. // anything in the range 0x80 to 0xa0 (which I reserve for my own private
  2359. // use in the text buffer). I ignore the repetition count.
  2360. // I also have to process some accelerators.
  2361. pageLine = caretLine;
  2362. // The following line is in effect following a request from F J Wright. He
  2363. // pointed out that the character that gets pressed when one is in page mode
  2364. // to un-pause the screen should be allowed to do just that and should not
  2365. // also appear as an input character. So here if I find a character being
  2366. // typed when things are paused I just lose it (as well as clearing the
  2367. // flags that pause things.
  2368. if (pagePaused)
  2369. { pagePaused = FALSE;
  2370. return;
  2371. }
  2372. inject_randomness(ch);
  2373. inject_randomness((int)time(NULL));
  2374. switch (ch)
  2375. {
  2376. case Ctrl('C'):
  2377. ToIcaret();
  2378. caretVisible = TRUE;
  2379. cwin_ensure_screen(FALSE); // may jump screen to caret location
  2380. OnInterrupt();
  2381. return;
  2382. // I make Ctrl+D and Ctrl+Z exit, but note very well that they both
  2383. // exit from this system super-promptly when the key is typed, and
  2384. // they do NOT wait until the program gets around to reading them.
  2385. case Ctrl('D'): // Some Unix users may be used to Ctrl+D as EOF?
  2386. OnExit();
  2387. return;
  2388. case Ctrl('G'):
  2389. ToIcaret();
  2390. caretVisible = TRUE;
  2391. cwin_ensure_screen(FALSE); // may jump screen to caret location
  2392. OnBacktrace();
  2393. return;
  2394. case Ctrl('I'): // TAB (I need to worry about the display of tabs!)
  2395. break;
  2396. case Ctrl('J'): // LINEFEED
  2397. ch = '\n';
  2398. break;
  2399. case Ctrl('L'):
  2400. OnRedraw();
  2401. return;
  2402. case Ctrl('M'): // CARRIAGE RETURN
  2403. ch = '\n';
  2404. case Ctrl('N'):
  2405. OnEnd(); // so that CR accepts the line always
  2406. break;
  2407. case Ctrl('O'):
  2408. OnCopy();
  2409. return;
  2410. case Ctrl('Q'):
  2411. if (pageMode) OnPageMode();
  2412. return;
  2413. case Ctrl('R'):
  2414. OnReInput();
  2415. return;
  2416. case Ctrl('S'):
  2417. if (!pageMode) OnPageMode();
  2418. return;
  2419. case Ctrl('V'):
  2420. OnPaste();
  2421. return;
  2422. case Ctrl('X'):
  2423. OnCut();
  2424. return;
  2425. case Ctrl('Z'): // Some PC users may be used to Ctrl+Z as EOF?
  2426. OnExit();
  2427. return;
  2428. case 0x1b: // ESC goes through to the user
  2429. break;
  2430. default:
  2431. if ((ch & 0x7f) < 0x20) return; // Discard control chars.
  2432. break;
  2433. }
  2434. CancelSelection();
  2435. ToIcaret();
  2436. caretVisible = TRUE;
  2437. cwin_ensure_screen(FALSE);
  2438. // If the caret is NOT at the end of the text then the insert operation I do
  2439. // here goes directly into the text buffer. If the caret IS at the end of the
  2440. // text I push the character into a type-ahead buffer, and beep if there
  2441. // was no room for it there.
  2442. if (caretChar != textLast)
  2443. { if (insertMode) cwin_caret_putchar(ch);
  2444. else cwin_caret_replacechar(ch);
  2445. }
  2446. else
  2447. { int p2 = (typeAheadP2 + 1) & typeAheadBufferMask;
  2448. if (p2 == typeAheadP1)
  2449. { ::MessageBeep(0xffffffff);
  2450. return;
  2451. }
  2452. typeAheadBuffer[typeAheadP2] = ch;
  2453. typeAheadP2 = p2;
  2454. }
  2455. }
  2456. void CMainWindow::ReplaceLastLine(unsigned char *s)
  2457. {
  2458. typeAheadP1 = typeAheadP2 = 0; // cancel type-ahead
  2459. selRootValid = FALSE;
  2460. OnEnd();
  2461. caretVisible = TRUE;
  2462. // The following loop deletes characters from the last line of the input,
  2463. // until what is left is (a) a totally empty buffer, (b) a buffer where the
  2464. // last line is empty or (c) the final thing in the buffer is a prompt
  2465. // item. While doing the deletion I will not update the screen at all.
  2466. while (textFirst != textLast &&
  2467. lineBuffer[lineLast].address != textLast)
  2468. { int n = (textLast-1) & TEXT_MASK;
  2469. if ((textBuffer[n]&0xff)==CH_ENDPROMPT) break;
  2470. if (textLast == caretChar) caretChar = n;
  2471. if (textLast == icaretChar) icaretChar = n;
  2472. if (textLast == inputLineStart) inputLineStart = n;
  2473. textLast = n;
  2474. }
  2475. int i=savedLines[currentInputLine];
  2476. for (;;)
  2477. { int ch = savedChars[i++];
  2478. if (ch == '\n') break;
  2479. if (i == MAX_SAVED_CHARS) i = 0;
  2480. cwin_putchar(ch);
  2481. }
  2482. CRect cr(0, LineY(lineLast),
  2483. clientWidth, LineY(lineLast)+LineDY(lineLast));
  2484. InvalidateRect(&cr);
  2485. LineSizes();
  2486. UpdateWindow();
  2487. cwin_ensure_screen(FALSE);
  2488. }
  2489. void CMainWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
  2490. {
  2491. CPoint cp;
  2492. int saveX, saveY;
  2493. pageLine = caretLine;
  2494. switch (ch)
  2495. {
  2496. // The INS key flips the status with respect to insert mode. I make this
  2497. // TRUE at the start of a run, and any time anybody does a PASTE operation.
  2498. case VK_INSERT:
  2499. insertMode = !insertMode;
  2500. return;
  2501. // I make the DEL key delete forwards by first simulating a right-arrow
  2502. // cursor movement effect and then performing the sort of deletion that
  2503. // I consider ordinary.
  2504. case VK_DELETE:
  2505. ToIcaret();
  2506. caretVisible = TRUE;
  2507. cwin_ensure_screen(FALSE);
  2508. if (selStartChar != selEndChar)
  2509. { DeleteSelection();
  2510. return;
  2511. }
  2512. if (caretChar == textLast) return;
  2513. OnKeyDown(VK_RIGHT, 1, nFlags);
  2514. if (caretChar != textLast)
  2515. { cwin_caret_unputchar();
  2516. return;
  2517. }
  2518. if (typeAheadP1 == typeAheadP2)
  2519. { typeAheadBuffer[typeAheadP2] = 0x7f;
  2520. typeAheadP2 = (typeAheadP2 + 1) & typeAheadBufferMask;
  2521. return;
  2522. }
  2523. typeAheadP2 = (typeAheadP2 - 1) & typeAheadBufferMask;
  2524. return;
  2525. case VK_BACK:
  2526. ToIcaret();
  2527. caretVisible = TRUE;
  2528. cwin_ensure_screen(FALSE); // may jump screen to caret location
  2529. // If there is a selection valid then DELETE will delete it, without any
  2530. // regard for where the caret is (although the caret will generally be
  2531. // at one or other end of the selected region).
  2532. if (selStartChar != selEndChar)
  2533. { DeleteSelection();
  2534. return;
  2535. }
  2536. // If the caret is NOT at the end of the text then DELETE has to work
  2537. // inside the buffer. This will typically make it a fairly expensive
  2538. // operation.
  2539. if (caretChar != textLast)
  2540. { cwin_caret_unputchar();
  2541. return;
  2542. }
  2543. // If there are no characters typed ahead I will put 0x7f into the
  2544. // input buffer. This case can arise in two different circumstances. The
  2545. // first is if a user types DELETE as the first character on a line. Then
  2546. // I will want to discard the junk characters later on. Otherwise it may be
  2547. // that the current line is partly displayed and is subject to interactive
  2548. // editing and then the 0x7f put in the buffer will be found again very
  2549. // soon and used to cause deletion of some prior character. Because of this
  2550. // latter case I will not beep on initial DELETE characters on a line.
  2551. if (typeAheadP1 == typeAheadP2)
  2552. { typeAheadBuffer[typeAheadP2] = 0x7f;
  2553. typeAheadP2 = (typeAheadP2 + 1) & typeAheadBufferMask;
  2554. return;
  2555. }
  2556. // If there are typed-ahead characters I can just discard the most recent. At
  2557. // this stage the character has not been displayed (eg a user typed XXX DEL
  2558. // before the application was ready to look at it) so the removal is really
  2559. // very easy.
  2560. typeAheadP2 = (typeAheadP2 - 1) & typeAheadBufferMask;
  2561. return;
  2562. // The various keys like "PAGE UP" etc do just what the scroll bar can do.
  2563. case VK_PRIOR:
  2564. OnVScroll(SB_PAGEUP, 0, NULL);
  2565. return;
  2566. case VK_NEXT:
  2567. OnVScroll(SB_PAGEDOWN, 0, NULL);
  2568. return;
  2569. case VK_HOME:
  2570. OnHome();
  2571. return;
  2572. case VK_END:
  2573. OnEnd();
  2574. return;
  2575. // The cursor arrow keys are funny here, with behaviour inspired by the
  2576. // DOSKEY history program. Right and left movement moves the caret, but
  2577. // up and down will bring back one of a number of stored input line.
  2578. case VK_UP:
  2579. // If my caret is on the last line I will do something DOSKEY-ish...
  2580. if (caretLine == lineLast)
  2581. { if (currentInputLine == savedP1) return;
  2582. if (currentInputLine == -1) currentInputLine = savedP2;
  2583. if (currentInputLine == 0) currentInputLine = MAX_SAVED_LINES;
  2584. currentInputLine--;
  2585. ReplaceLastLine(&savedChars[savedLines[currentInputLine]]);
  2586. return;
  2587. }
  2588. if (caretLine == lineFirst) return;
  2589. if (caretLine == lineVisible) OnVScroll(SB_LINEUP, 0, NULL);
  2590. cp.x = caretX-xOffset;
  2591. cp.y = caretY - 1;
  2592. FindMouseChar(cp);
  2593. caretVisible = TRUE;
  2594. cwin_ensure_screen(FALSE);
  2595. return;
  2596. case VK_DOWN:
  2597. // If my caret is on the last line I will do something DOSKEY-ish...
  2598. if (caretLine == lineLast)
  2599. { int n = currentInputLine + 1;
  2600. if (n == MAX_SAVED_LINES) n = 0;
  2601. if (currentInputLine == -1 || n == savedP2) return;
  2602. currentInputLine = n;
  2603. ReplaceLastLine(&savedChars[savedLines[currentInputLine]]);
  2604. return;
  2605. }
  2606. cp.x = caretX-xOffset;
  2607. saveY = caretY, saveX = caretX;
  2608. cp.y = caretY + LineDY(caretLine) + 1;
  2609. // The case when I am at the bottom of the screen and hit the "DOWN" key
  2610. // is a bit of a mess. I call FindMouse Char as usual to try to re-position
  2611. // the caret. If that would be outside the window I get a flag set to tell me
  2612. // so. In that case I re-position the caret where it just came from, then
  2613. // scroll the window, and finally repeat my attempt to re-position the
  2614. // caret.
  2615. FindMouseChar(cp);
  2616. if (mouseOutside & 8)
  2617. { cp.x = saveX-xOffset;
  2618. cp.y = saveY + 1;
  2619. FindMouseChar(cp);
  2620. OnVScroll(SB_LINEDOWN, 0, NULL);
  2621. cp.x = caretX-xOffset;
  2622. cp.y = caretY + LineDY(caretLine) + 1;
  2623. FindMouseChar(cp);
  2624. }
  2625. caretVisible = TRUE;
  2626. cwin_ensure_screen(FALSE);
  2627. return;
  2628. case VK_LEFT:
  2629. caretVisible = TRUE;
  2630. if (textFirst == caretChar) return; // at start of buffer?
  2631. if (lineBuffer[caretLine].address == caretChar) // back over a newline?
  2632. { int n = (caretLine-1)&LINE_MASK;
  2633. caretX = lineBuffer[n].width;
  2634. caretLine=n;
  2635. n = lineBuffer[n].address;
  2636. while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
  2637. caretChar = n;
  2638. }
  2639. else
  2640. { int n = (caretChar-1) & TEXT_MASK;
  2641. int w = windowFonts.HCourier.across['X'];
  2642. if ((textBuffer[n]&0xff)==CH_ENDPROMPT) // move over whole of a prompt
  2643. { do
  2644. { n = (n-1)&TEXT_MASK;
  2645. caretX -= w;
  2646. } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT);
  2647. caretChar = n;
  2648. if (lineBuffer[caretLine].address == caretChar) // back over a newline?
  2649. { n = (caretLine-1)&LINE_MASK;
  2650. caretX = lineBuffer[n].width;
  2651. caretLine=n;
  2652. n = lineBuffer[n].address;
  2653. while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
  2654. caretChar = n;
  2655. }
  2656. }
  2657. else caretX -= w;
  2658. if (caretLine == lineLast) icaretChar = n, icaretLine = lineLast;
  2659. caretChar = n;
  2660. // The amount of invalidation done here runs across to the end of the
  2661. // last line, and that ought to get rid of any residues of the old
  2662. // caret. I use an APPROXIMATION to the new value of caretX, but hope that
  2663. // that gets corrected during the re-painting.
  2664. CRect cr(caretX-xOffset, LineY(caretLine),
  2665. clientWidth, LineY(caretLine)+LineDY(caretLine));
  2666. InvalidateRect(&cr); // Should I hide the caret here?
  2667. }
  2668. UpdateWindow();
  2669. cwin_ensure_screen(FALSE);
  2670. return;
  2671. case VK_RIGHT:
  2672. caretVisible = TRUE;
  2673. if (textLast == caretChar) return; // at end of buffer?
  2674. if (textBuffer[caretChar] == '\n') // at a newline?
  2675. { caretX = 0;
  2676. int w = windowFonts.HCourier.across['X'];
  2677. caretLine = (caretLine+1)&LINE_MASK;
  2678. caretChar = lineBuffer[caretLine].address;
  2679. if (caretChar!=textLast &&
  2680. (textBuffer[caretChar]&0xff)==CH_PROMPT) // move over whole of a prompt
  2681. { while (caretChar!=textLast &&
  2682. (textBuffer[caretChar]&0xff)!=CH_ENDPROMPT)
  2683. { caretChar = (caretChar+1)&TEXT_MASK;
  2684. caretX += w;
  2685. }
  2686. caretX -= w;
  2687. if (caretChar!=textLast) caretChar = (caretChar+1)&TEXT_MASK;
  2688. }
  2689. CPoint caretPos(caretX-xOffset, caretY);
  2690. SetCaretPos(caretPos);
  2691. if (caretLine == lineLast)
  2692. { icaretLine = caretLine;
  2693. icaretChar = caretChar;
  2694. icaretX = caretX;
  2695. }
  2696. }
  2697. else
  2698. { int n = caretChar;
  2699. int oldCaretX = caretX;
  2700. int w = windowFonts.HCourier.across['X'];
  2701. n = (n+1)&TEXT_MASK;
  2702. caretX += w;
  2703. if (caretLine == lineLast) icaretChar = n, icaretLine = caretLine;
  2704. caretChar = n;
  2705. CRect cr(oldCaretX-xOffset, LineY(caretLine),
  2706. caretX-xOffset+w, LineY(caretLine)+LineDY(caretLine));
  2707. InvalidateRect(&cr); // Should I hide the caret here?
  2708. }
  2709. UpdateWindow();
  2710. cwin_ensure_screen(FALSE);
  2711. return;
  2712. }
  2713. }
  2714. // The next function takes a mouse position and identifies the character in
  2715. // the text buffer that it identifies. It must be kept well in step with
  2716. // the corresponding code in PaintTextLine().
  2717. int CMainWindow::FindMouseChar(CPoint point)
  2718. {
  2719. // I may have captured the mouse, in which case the coordinates returned may
  2720. // be outside my client area. Clip them to it before doing anything else.
  2721. int x = point.x, y = point.y;
  2722. int nCount = 0;
  2723. mouseOutside = 0;
  2724. if (x<0) x=0, mouseOutside |= 1;
  2725. else if (x>clientWidth) x=clientWidth, mouseOutside |= 2;
  2726. if (y<0) y=0, x=0, mouseOutside |= 4;
  2727. else if (y>clientHeight) y=clientHeight, x=clientWidth, mouseOutside |= 8;
  2728. if (mouseOutside == 0) selectScrollSpeed = 0;
  2729. int yOffset = lineBuffer[lineVisible].position;
  2730. int line;
  2731. for (line=lineVisible;;line=(line+1)&LINE_MASK)
  2732. { int y1 = lineBuffer[line].position - yOffset;
  2733. int y2 = y1 + lineBuffer[line].height;
  2734. if (y>=y1 && y<=y2) break;
  2735. if (line==lineLast) break;
  2736. }
  2737. // Now I have identified the line that the mouse cursor is on.
  2738. int textChar = lineBuffer[line].address;
  2739. int activeFont = CH_COURIER;
  2740. unsigned char *activeWidths = windowFonts.HCourier.across;
  2741. int xc = -xOffset;
  2742. int inPrompt = -1, inPromptX;
  2743. for (;;textChar=(textChar+1)&TEXT_MASK)
  2744. { if (textChar==textLast) break;
  2745. int c = textBuffer[textChar];
  2746. int w = activeWidths[c & 0xff];
  2747. switch (c & 0xff)
  2748. {
  2749. case '\n':
  2750. break;
  2751. case CH_PROMPT:
  2752. inPrompt = textChar, inPromptX = xc;
  2753. continue;
  2754. case CH_ENDPROMPT:
  2755. inPrompt = -1;
  2756. continue;
  2757. case CH_RED:
  2758. // case CH_BLUE:
  2759. // case CH_BLACK:
  2760. case CH_GRAY:
  2761. continue;
  2762. case CH_COURIER:
  2763. activeFont = c;
  2764. activeWidths = windowFonts.HCourier.across;
  2765. continue;
  2766. case CH_ROMAN:
  2767. activeFont = c;
  2768. activeWidths = windowFonts.HRoman.across;
  2769. continue;
  2770. case CH_BOLD:
  2771. activeFont = c;
  2772. activeWidths = windowFonts.HBold.across;
  2773. continue;
  2774. case CH_ITALIC:
  2775. activeFont = c;
  2776. activeWidths = windowFonts.HItalic.across;
  2777. continue;
  2778. case CH_SYMBOL:
  2779. activeFont = c;
  2780. activeWidths = windowFonts.HSymbol.across;
  2781. continue;
  2782. case CH_Roman:
  2783. activeFont = c;
  2784. activeWidths = windowFonts.Hroman.across;
  2785. continue;
  2786. case CH_Bold:
  2787. activeFont = c;
  2788. activeWidths = windowFonts.Hbold.across;
  2789. continue;
  2790. case CH_Italic:
  2791. activeFont = c;
  2792. activeWidths = windowFonts.Hitalic.across;
  2793. continue;
  2794. case CH_Symbol:
  2795. activeFont = c;
  2796. activeWidths = windowFonts.Hsymbol.across;
  2797. continue;
  2798. default:
  2799. if (c == '\t')
  2800. { c = ' ';
  2801. w = currentWidths[' ']*(8-(nCount&7));
  2802. nCount = 7;
  2803. }
  2804. else if (c < 32) w = activeWidths['^'] + activeWidths[c & 0xff],
  2805. nCount++;
  2806. if (x <= xc+w/2)
  2807. {
  2808. // I will not allow the user to select a position within a prompt, since
  2809. // if I did it would make it possible to CUT or DELete part of the prompt
  2810. // string, including either the control character that marked its start or
  2811. // its end. The result would be that the status of the text as "prompt" would
  2812. // be lost with possible consequent confusion.
  2813. if (inPrompt>=0) textChar=inPrompt, xc=inPromptX;
  2814. break; // Bingo! Found it.
  2815. }
  2816. xc += w;
  2817. nCount++;
  2818. continue;
  2819. }
  2820. break;
  2821. }
  2822. // If the mouse click moves me to the last line then I place the
  2823. // input caret there. Otherwise if a previous caret had been on the last line
  2824. // I leave the input caret where the previous caret had been.
  2825. if (line == lineLast)
  2826. { icaretChar = textChar;
  2827. icaretX = xc+xOffset;
  2828. icaretFontWidths = activeWidths;
  2829. icaretLine = line;
  2830. }
  2831. else if (caretLine == lineLast)
  2832. { icaretChar = caretChar;
  2833. icaretX = caretX;
  2834. icaretFontWidths = caretFontWidths;
  2835. icaretLine = caretLine;
  2836. }
  2837. caretChar = textChar;
  2838. caretX = xc+xOffset;
  2839. caretFontWidths = activeWidths;
  2840. caretLine = line;
  2841. inject_randomness(caretX);
  2842. inject_randomness(caretLine);
  2843. // In the case that mouse activity re-positions the caret I will move the
  2844. // caret on the screen directly to where it needs to be shown. No benefit
  2845. // would come from delaying until a future re-paint operation.
  2846. HideCaret();
  2847. CPoint caretPos(caretX-xOffset, caretY);
  2848. SetCaretPos(caretPos);
  2849. ShowCaret();
  2850. return caretChar;
  2851. }
  2852. // StartSelection is slightly curious in that it sets the selection
  2853. // to be empty but rooted at a specific point. The effect will be that
  2854. // a subsequent MouseMove or shift-click can extend the selection to
  2855. // be non-trivial. The ugly feature of this is that a selection that has
  2856. // been "started" in this way could suffer if the contents of the text buffer
  2857. // moved. To prevent trouble I will keep a flag that indicates when I have
  2858. // a selection starting-point that I will count as valid. I will set this
  2859. // flag here and clear it anywhere where I move or destroy bits of the
  2860. // buffer.
  2861. void CMainWindow::StartSelection()
  2862. {
  2863. CancelSelection();
  2864. selFirstChar = selStartChar = selEndChar = caretChar;
  2865. selFirstX = selStartX = selEndX = caretX;
  2866. selFirstLine = selStartLine = selEndLine = caretLine;
  2867. selRootValid = TRUE;
  2868. }
  2869. void CMainWindow::InvalidateSelection(int l1, int x1, int l2, int x2)
  2870. {
  2871. int w;
  2872. if (l1 > l2)
  2873. { w=l1, l1=l2, l2=w, w=x1, x1=x2, x2=w;
  2874. if (x2 == 0) l2=(l2-1)&LINE_MASK, x2=clientWidth+xOffset;
  2875. }
  2876. HideCaret();
  2877. if (l1==l2)
  2878. { if (x1>x2) w=x1, x1=x2, x2=w;
  2879. CRect r(x1-xOffset, LineY(l1),
  2880. x2+caretWidth-xOffset, LineY(l1)+LineDY(l1));
  2881. if (x1 != x2) InvalidateRect(&r);
  2882. }
  2883. else
  2884. { CRect r(0, LineY(l1), clientWidth, LineY(l2)+LineDY(l2));
  2885. InvalidateRect(&r);
  2886. }
  2887. ShowCaret();
  2888. }
  2889. void CMainWindow::ExtendSelection()
  2890. {
  2891. int oldStart = selStartChar, oldEnd = selEndChar;
  2892. if (selFirstChar == selStartChar)
  2893. InvalidateSelection(selEndLine, selEndX, caretLine, caretX);
  2894. else InvalidateSelection(selStartLine, selStartX, caretLine, caretX);
  2895. if (betweenChar(textFirst, caretChar, selFirstChar))
  2896. { selStartChar = caretChar; selStartX = caretX; selStartLine = caretLine;
  2897. selEndChar = selFirstChar; selEndX = selFirstX; selEndLine = selFirstLine;
  2898. }
  2899. else
  2900. { selStartChar = selFirstChar; selStartX = selFirstX; selStartLine = selFirstLine;
  2901. selEndChar = caretChar; selEndX = caretX; selEndLine = caretLine;
  2902. }
  2903. if (selStartChar==oldStart && selEndChar==oldEnd) return;
  2904. }
  2905. void CMainWindow::CancelSelection()
  2906. {
  2907. InvalidateSelection(selStartLine, selStartX, selEndLine, selEndX);
  2908. selFirstChar = selStartChar = selEndChar = caretChar;
  2909. selFirstX = selStartX = selEndX = caretX;
  2910. selFirstLine = selStartLine = selEndLine = caretLine;
  2911. }
  2912. void CMainWindow::OnLButtonDblClk(UINT nFlags, CPoint point)
  2913. {
  2914. }
  2915. void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point)
  2916. {
  2917. /*
  2918. * The idea here is that a first click within a window should NOT move
  2919. * the caret or start a new selection, since it was probably (?) the
  2920. * mouse event that just caused the window to become active again.
  2921. */
  2922. if (pagePaused || hasFocus) return;
  2923. FindMouseChar(point);
  2924. trackingSelection = TRUE;
  2925. SetCapture();
  2926. selectScrollSpeed = 0;
  2927. if (nFlags & MK_SHIFT && selRootValid) ExtendSelection();
  2928. else StartSelection();
  2929. }
  2930. void CMainWindow::OnLButtonUp(UINT nFlags, CPoint point)
  2931. {
  2932. if (hasFocus)
  2933. { hasFocus = FALSE;
  2934. return;
  2935. }
  2936. if (pagePaused) pageLine = caretLine;
  2937. else
  2938. { FindMouseChar(point);
  2939. ::ReleaseCapture();
  2940. if (trackingSelection) ExtendSelection();
  2941. trackingSelection = FALSE;
  2942. selectScrollSpeed = 0;
  2943. }
  2944. }
  2945. void CMainWindow::OnMButtonDblClk(UINT nFlags, CPoint point)
  2946. {
  2947. }
  2948. void CMainWindow::OnMButtonDown(UINT nFlags, CPoint point)
  2949. {
  2950. }
  2951. void CMainWindow::OnMButtonUp(UINT nFlags, CPoint point)
  2952. {
  2953. if (hasFocus)
  2954. { hasFocus = FALSE;
  2955. return;
  2956. }
  2957. pageLine = caretLine;
  2958. }
  2959. void CMainWindow::OnRButtonDblClk(UINT nFlags, CPoint point)
  2960. {
  2961. }
  2962. void CMainWindow::OnRButtonDown(UINT nFlags, CPoint point)
  2963. {
  2964. }
  2965. void CMainWindow::OnRButtonUp(UINT nFlags, CPoint point)
  2966. {
  2967. if (hasFocus)
  2968. { hasFocus = FALSE;
  2969. return;
  2970. }
  2971. pageLine = caretLine;
  2972. }
  2973. void CMainWindow::OnMouseMove(UINT nFlags, CPoint point)
  2974. {
  2975. inject_randomness((int)clock());
  2976. if (trackingSelection && !pagePaused)
  2977. { FindMouseChar(point);
  2978. if (mouseOutside != 0)
  2979. { if (selectScrollSpeed == 0)
  2980. selectScrollSpeed = 7,
  2981. selectScrollCount = 1;
  2982. selectScrollDirection = mouseOutside;
  2983. selectScrollPoint = point;
  2984. OnTimer(1);
  2985. }
  2986. else ExtendSelection();
  2987. }
  2988. }
  2989. void CMainWindow::OnTimer(UINT timerId)
  2990. {
  2991. if (selectScrollSpeed == 0) return;
  2992. selectScrollCount--;
  2993. if (selectScrollCount > 0) return;
  2994. if (selectScrollDirection == 0) return;
  2995. if (selectScrollDirection & 8) OnVScroll(SB_LINEDOWN, 0, NULL);
  2996. else if (selectScrollDirection & 4) OnVScroll(SB_LINEUP, 0, NULL);
  2997. else if (selectScrollDirection & 1 && xOffset!=0)
  2998. OnHScroll(SB_LINELEFT, 0, NULL);
  2999. else if (selectScrollDirection & 2 &&
  3000. caretX < lineBuffer[caretLine].width)
  3001. OnHScroll(SB_LINERIGHT, 0, NULL);
  3002. selectScrollCount = selectScrollSpeed;
  3003. if (selectScrollSpeed > 1) selectScrollSpeed--;
  3004. FindMouseChar(selectScrollPoint);
  3005. ExtendSelection();
  3006. }
  3007. void CMainWindow::OnNcLButtonDown(UINT nFlags, CPoint point)
  3008. {
  3009. }
  3010. void CMainWindow::OnNcMButtonDown(UINT nFlags, CPoint point)
  3011. {
  3012. }
  3013. void CMainWindow::OnNcRButtonDown(UINT nFlags, CPoint point)
  3014. {
  3015. }
  3016. //
  3017. // Now the things provoked from the menus...
  3018. //
  3019. // SAVEAS will dump everything that is in the text buffer into a
  3020. // log file. The status of prompts and any other funny stuff will not
  3021. // concern me here - I will just dump out bytes as they appear in the
  3022. // text buffer. But at least I will arrange that I do not dump any characters
  3023. // that have a code >= 0x80 into the saved file.
  3024. void CMainWindow::OnSaveAs()
  3025. {
  3026. CFileDialog FDialog(FALSE, "LOG", "savefile.log",
  3027. OFN_HIDEREADONLY,
  3028. "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
  3029. FDialog.m_ofn.lpstrTitle = "Save As";
  3030. if (FDialog.DoModal() != IDOK) return;
  3031. FILE *ofile = fopen(FDialog.GetPathName(), "w");
  3032. if (ofile == NULL)
  3033. { DisplayMsg("Could not write to file");
  3034. cwin_ensure_screen(FALSE);
  3035. return;
  3036. }
  3037. for (int i=lineFirst; i!=lineLast; i=(i+1)&LINE_MASK)
  3038. { int p = lineBuffer[i].address, c;
  3039. while ((c = textBuffer[p]) != '\n')
  3040. { if ((c & 0x80) == 0) putc(c, ofile);
  3041. p = (p+1) & TEXT_MASK;
  3042. }
  3043. putc('\n', ofile);
  3044. }
  3045. int p = lineBuffer[lineLast].address;
  3046. while (p != textLast)
  3047. { int c = textBuffer[p];
  3048. if ((c & 0x80) == 0) putc(c, ofile);
  3049. p = (p+1) & TEXT_MASK;
  3050. }
  3051. putc('\n', ofile); // extra newline at end to be tidy.
  3052. fclose(ofile);
  3053. cwin_ensure_screen(FALSE);
  3054. }
  3055. void CMainWindow::OnSaveSel()
  3056. {
  3057. CFileDialog FDialog(FALSE, "LOG", "savesel.log",
  3058. OFN_HIDEREADONLY,
  3059. "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
  3060. FDialog.m_ofn.lpstrTitle = "Save Selection";
  3061. if (FDialog.DoModal() != IDOK) return;
  3062. FILE *ofile = fopen(FDialog.GetPathName(), "w");
  3063. if (ofile == NULL)
  3064. { DisplayMsg("Could not write to file");
  3065. cwin_ensure_screen(FALSE);
  3066. return;
  3067. }
  3068. // print selected region...
  3069. int l1 = selStartLine;
  3070. int cp = selStartChar;
  3071. for (;;)
  3072. { while (cp!=selEndChar && textBuffer[cp]!='\n')
  3073. { int c = textBuffer[cp];
  3074. if ((c & 0x80) == 0) putc(c, ofile);
  3075. cp = (cp+1)&TEXT_MASK;
  3076. }
  3077. if (cp==selEndChar) break;
  3078. putc('\n', ofile);
  3079. l1 = (l1+1)&LINE_MASK;
  3080. cp = lineBuffer[l1].address;
  3081. }
  3082. fclose(ofile);
  3083. cwin_ensure_screen(FALSE);
  3084. }
  3085. void CMainWindow::OnToFile()
  3086. {
  3087. // When I have a log file active I will make the menu entry into
  3088. // one that closes the transcript, while if I do not have a file in
  3089. // use the File/&T menu entry will start one off.
  3090. //
  3091. // NOTE that the "spool_file" mentioned here belongs somewhere in CSL,
  3092. // and is not in the "cwin" code in any more direct way.
  3093. if (spool_file != NULL)
  3094. { fprintf(spool_file, "\n+++ End of transcript +++\n");
  3095. fclose(spool_file);
  3096. spool_file = NULL;
  3097. GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND,
  3098. IDM_TOFILE, "&To File...");
  3099. DrawMenuBar();
  3100. cwin_ensure_screen(FALSE);
  3101. return;
  3102. }
  3103. CFileDialog FDialog(FALSE, "LOG", "logfile.log",
  3104. OFN_HIDEREADONLY,
  3105. "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
  3106. FDialog.m_ofn.lpstrTitle = "Transcript File";
  3107. if (FDialog.DoModal() != IDOK) return;
  3108. FILE *ofile = fopen(FDialog.GetPathName(), "w");
  3109. if (ofile == NULL)
  3110. { DisplayMsg("Could not write to file");
  3111. cwin_ensure_screen(FALSE);
  3112. return;
  3113. }
  3114. spool_file = ofile;
  3115. time_t t0 = time(NULL);
  3116. fprintf(spool_file, "+++ Transcript started at %.24s +++\n", ctime(&t0));
  3117. GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND,
  3118. IDM_TOFILE, "&Terminate log");
  3119. DrawMenuBar();
  3120. cwin_ensure_screen(FALSE);
  3121. }
  3122. static CFont *MakeNewFont(CDC *dc, FontHeights *fh, DWORD charSet,
  3123. const char *fontName, int weight,
  3124. DWORD italic, int height)
  3125. {
  3126. CFont *newFont = new CFont;
  3127. newFont->CreateFont(
  3128. height, 0, // height, width
  3129. 0, 0, // angle of escapement, base line orientation angle
  3130. weight, italic, // weight, italic-flag
  3131. 0, 0, charSet, // underline, strike-out, character-set
  3132. OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
  3133. DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE,
  3134. fontName);
  3135. dc->SelectObject(newFont);
  3136. TEXTMETRIC mm;
  3137. dc->GetTextMetrics(&mm);
  3138. fh->up = mm.tmExternalLeading + mm.tmAscent;
  3139. fh->down = mm.tmDescent;
  3140. fh->height = mm.tmHeight;
  3141. int widths[256], i;
  3142. dc->GetCharWidth(0, 255, widths);
  3143. for (i=0x80; i<0xa0; i++) widths[i] = 0; // my control characters
  3144. // The width of a character in any sensible sized font will be less than
  3145. // 256 pixels, so I save some space by stashing the information in a byte
  3146. // array rather than a word array. If I really knew I was only ever going to
  3147. // use TrueType fonts maybe I ought to use ABCWidths rather than just
  3148. // widths. At present I think that would count as overkill.
  3149. for (i=0; i<256; i++) fh->across[i] = (unsigned char)widths[i];
  3150. return newFont;
  3151. }
  3152. void CMainWindow::OnPrint()
  3153. {
  3154. CPrintDialog pd(FALSE);
  3155. if (pd.DoModal() != IDOK)
  3156. { cwin_ensure_screen(FALSE);
  3157. return;
  3158. }
  3159. CDC PrintDC;
  3160. if (PrintDC.CreateDC(pd.GetDriverName(), pd.GetDeviceName(),
  3161. pd.GetPortName(), NULL))
  3162. { int printWidth = PrintDC.GetDeviceCaps(HORZRES),
  3163. printHeight = PrintDC.GetDeviceCaps(VERTRES);
  3164. // Also get the size of the print area in mm.
  3165. int sizeX = PrintDC.GetDeviceCaps(HORZRES), // measured in pixels
  3166. sizeY = PrintDC.GetDeviceCaps(VERTRES);
  3167. int mmX = PrintDC.GetDeviceCaps(HORZSIZE), // measured in mm
  3168. mmY = PrintDC.GetDeviceCaps(VERTSIZE);
  3169. // I will try to allow a 10mm margin to right & left and a 15mm at top
  3170. //and bottom of the printed page.
  3171. int offsetX = (10*sizeX)/mmX;
  3172. int offsetY = (15*sizeY)/mmY;
  3173. sizeX -= 2*offsetX;
  3174. sizeY -= 2*offsetY;
  3175. // Create printer fonts, using the same type face as is present on the
  3176. // screen. Scale it so that around 80 columns will fit across the page.
  3177. // I scale for 81 not 80 to leave a margin for rounding etc etc.
  3178. FontHeights fh;
  3179. CFont *f = MakeNewFont(&PrintDC, &fh, ANSI_CHARSET,
  3180. (char *)windowFonts.baseFace,
  3181. windowFonts.boldFlag, 0, 100);
  3182. PrintDC.SelectStockObject(SYSTEM_FONT);
  3183. delete f;
  3184. int X = fh.across['X'];
  3185. int newH = (fh.height * sizeX)/(81 * X);
  3186. printerFonts.ChangeFont(&PrintDC, newH,
  3187. windowFonts.boldFlag, windowFonts.baseFace);
  3188. DOCINFO DocInfo;
  3189. DocInfo.cbSize = sizeof(DOCINFO);
  3190. DocInfo.lpszDocName = "Screen Dump";
  3191. DocInfo.lpszOutput = NULL;
  3192. if (PrintDC.StartDoc(&DocInfo) != -1)
  3193. {
  3194. PrintDC.StartPage();
  3195. PrintDC.SetTextAlign(TA_BASELINE);
  3196. PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK;
  3197. PrintDC.SetBkColor(windowColour);
  3198. PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
  3199. currentWidths = printerFonts.HCourier.across;
  3200. int y = offsetY, line;
  3201. for (line=lineFirst;; line=(line+1)&LINE_MASK)
  3202. { int up, down;
  3203. MeasureLine(lineBuffer[line].address,
  3204. &up, &down, &printerFonts);
  3205. int y1 = y+up;
  3206. int y2 = y+up+down;
  3207. if (y2 > sizeY)
  3208. { PrintDC.EndPage();
  3209. PrintDC.StartPage();
  3210. PrintDC.SetTextAlign(TA_BASELINE);
  3211. PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK;
  3212. PrintDC.SetBkColor(windowColour);
  3213. PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
  3214. currentWidths = printerFonts.HCourier.across;
  3215. y = offsetY;
  3216. y1 = y+up;
  3217. y2 = y+up+down;
  3218. }
  3219. PaintTextLine(&PrintDC, offsetX, y, y1, y2,
  3220. lineBuffer[line].address,
  3221. sizeX, &printerFonts, 1);
  3222. y = y2;
  3223. if (line==lineLast) break;
  3224. }
  3225. PrintDC.SelectStockObject(BLACK_PEN);
  3226. PrintDC.EndPage();
  3227. PrintDC.EndDoc();
  3228. }
  3229. printerFonts.DeleteFonts();
  3230. }
  3231. GlobalFree(pd.m_pd.hDevMode);
  3232. GlobalFree(pd.m_pd.hDevNames);
  3233. cwin_ensure_screen(FALSE);
  3234. }
  3235. void CMainWindow::OnPrintSel()
  3236. {
  3237. if (selStartChar == selEndChar) return; // nothing selected!
  3238. CPrintDialog pd(FALSE);
  3239. if (pd.DoModal() != IDOK)
  3240. { cwin_ensure_screen(FALSE);
  3241. return;
  3242. }
  3243. CDC PrintDC;
  3244. if (PrintDC.CreateDC(pd.GetDriverName(), pd.GetDeviceName(),
  3245. pd.GetPortName(), NULL))
  3246. { int printWidth = PrintDC.GetDeviceCaps(HORZRES),
  3247. printHeight = PrintDC.GetDeviceCaps(VERTRES);
  3248. // Also get the size of the print area in mm.
  3249. int sizeX = PrintDC.GetDeviceCaps(HORZRES), // measured in pixels
  3250. sizeY = PrintDC.GetDeviceCaps(VERTRES);
  3251. int mmX = PrintDC.GetDeviceCaps(HORZSIZE), // measured in mm
  3252. mmY = PrintDC.GetDeviceCaps(VERTSIZE);
  3253. // I will try to allow a 10mm margin to right & left and a 15mm at top
  3254. //and bottom of the printed page.
  3255. int offsetX = (10*sizeX)/mmX;
  3256. int offsetY = (15*sizeY)/mmY;
  3257. sizeX -= 2*offsetX;
  3258. sizeY -= 2*offsetY;
  3259. // Create printer fonts, using the same type face as is present on the
  3260. // screen. Scale it so that around 80 columns will fit across the page.
  3261. // I scale for 81 not 80 to leave a margin for rounding etc etc.
  3262. FontHeights fh;
  3263. CFont *f = MakeNewFont(&PrintDC, &fh, ANSI_CHARSET,
  3264. (char *)windowFonts.baseFace,
  3265. windowFonts.boldFlag, 0, 100);
  3266. PrintDC.SelectStockObject(SYSTEM_FONT);
  3267. delete f;
  3268. int X = fh.across['X'];
  3269. int newH = (fh.height * sizeX)/(81 * X);
  3270. printerFonts.ChangeFont(&PrintDC, newH,
  3271. windowFonts.boldFlag, windowFonts.baseFace);
  3272. DOCINFO DocInfo;
  3273. DocInfo.cbSize = sizeof(DOCINFO);
  3274. DocInfo.lpszDocName = "Selection Dump";
  3275. DocInfo.lpszOutput = NULL;
  3276. if (PrintDC.StartDoc(&DocInfo) != -1)
  3277. {
  3278. PrintDC.StartPage();
  3279. PrintDC.SetTextAlign(TA_BASELINE);
  3280. PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK;
  3281. PrintDC.SetBkColor(windowColour);
  3282. PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
  3283. currentWidths = printerFonts.HCourier.across;
  3284. int y = offsetY, line;
  3285. for (line=selStartLine;; line=(line+1)&LINE_MASK)
  3286. { int up, down;
  3287. MeasureLine(lineBuffer[line].address,
  3288. &up, &down, &printerFonts);
  3289. int y1 = y+up;
  3290. int y2 = y+up+down;
  3291. if (y2 > sizeY)
  3292. { PrintDC.EndPage();
  3293. PrintDC.StartPage();
  3294. PrintDC.SetTextAlign(TA_BASELINE);
  3295. PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK;
  3296. PrintDC.SetBkColor(windowColour);
  3297. PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
  3298. currentWidths = printerFonts.HCourier.across;
  3299. y = offsetY;
  3300. y1 = y+up;
  3301. y2 = y+up+down;
  3302. }
  3303. PaintTextLine(&PrintDC, offsetX, y, y1, y2,
  3304. lineBuffer[line].address,
  3305. sizeX, &printerFonts, 2);
  3306. y = y2;
  3307. if (line==selEndLine) break;
  3308. }
  3309. PrintDC.SelectStockObject(BLACK_PEN);
  3310. PrintDC.EndPage();
  3311. PrintDC.EndDoc();
  3312. }
  3313. printerFonts.DeleteFonts();
  3314. }
  3315. GlobalFree(pd.m_pd.hDevMode);
  3316. GlobalFree(pd.m_pd.hDevNames);
  3317. cwin_ensure_screen(FALSE);
  3318. }
  3319. void CMainWindow::OnCut()
  3320. {
  3321. OnCopy();
  3322. DeleteSelection();
  3323. cwin_ensure_screen(FALSE);
  3324. }
  3325. void CMainWindow::DeleteSelection()
  3326. {
  3327. int p = selStartChar, q = selEndChar;
  3328. if (betweenChar(p, caretChar, q))
  3329. { caretChar = p;
  3330. caretLine = selStartLine;
  3331. // In general after a delete operation caretX will not be valid, but the
  3332. // paint operation puts that right soon enough.
  3333. //-- caretX = selStartX;
  3334. }
  3335. if (betweenChar(p, icaretChar, q))
  3336. { icaretChar = p;
  3337. icaretLine = selStartLine;
  3338. // In general after a delete operation icaretX will not be valid, but the
  3339. // paint operation puts that right soon enough.
  3340. //-- icaretX = selStartX;
  3341. }
  3342. if (selStartLine==selEndLine)
  3343. { while (q!=textLast)
  3344. { int c = textBuffer[q];
  3345. textBuffer[p] = c;
  3346. if (caretChar==q) caretChar = p;
  3347. if (icaretChar==q) icaretChar = p;
  3348. if (inputLineStart==q) inputLineStart=p;
  3349. if (c == '\n') break;
  3350. p = (p+1)&TEXT_MASK;
  3351. q = (q+1)&TEXT_MASK;
  3352. }
  3353. if (textLast==q) textLast = p;
  3354. if (inputLineStart==q) inputLineStart=p;
  3355. if (caretChar==q) caretChar=p;
  3356. if (icaretChar==q) icaretChar=p;
  3357. // The caret will have its position re-calculated during the re-draw, and also
  3358. // the line-length record will be adjusted then.
  3359. InvalidateSelection(selStartLine, selStartX,
  3360. selStartLine, clientWidth+xOffset);
  3361. }
  3362. else
  3363. { int l = selStartLine, l1 = selEndLine;
  3364. lineLast = l;
  3365. for (;;)
  3366. { while (q!=textLast)
  3367. { int c = textBuffer[q];
  3368. textBuffer[p] = c;
  3369. if (caretChar==q) caretChar=q, caretLine = l;
  3370. if (inputLineStart==q) inputLineStart=p;
  3371. if (icaretChar==q) icaretChar=p, icaretLine = l;
  3372. p = (p+1)&TEXT_MASK;
  3373. q = (q+1)&TEXT_MASK;
  3374. if (c=='\n') break;
  3375. }
  3376. if (q==textLast) break;
  3377. l1 = (l1+1)&LINE_MASK;
  3378. q = lineBuffer[l1].address;
  3379. l = (l+1)&LINE_MASK;
  3380. lineBuffer[l].address = p;
  3381. lineLast = l;
  3382. }
  3383. if (inputLineStart==q) inputLineStart=p;
  3384. if (caretChar==q) caretChar=p;
  3385. if (icaretChar==q) caretChar=p;
  3386. textLast = p;
  3387. LineSizes();
  3388. // I invalidate down to the bottom of the screen. In some cases maybe I
  3389. // count do a ScrollWindow to good effect, and the plain invalidation
  3390. // here may cause the screen to flicker more than would be ideal.
  3391. CRect cr(0, LineY(selStartLine), clientWidth, clientHeight);
  3392. InvalidateRect(&cr);
  3393. }
  3394. selFirstChar = selEndChar = selStartChar;
  3395. selFirstX = selEndX = selStartX;
  3396. selFirstLine = selEndLine = selStartLine;
  3397. // The following sequence of operations is intended to scroll the window
  3398. // (if necessary) to make the caret visible, to get scroll bar thumbs
  3399. // up to date (eg after a multi-line delete) etc.
  3400. UpdateWindow();
  3401. OnSize(SIZE_RESTORED, clientWidth, clientHeight);
  3402. HideCaret();
  3403. CPoint caretPos(caretX-xOffset, caretY);
  3404. SetCaretPos(caretPos);
  3405. ShowCaret();
  3406. }
  3407. void CMainWindow::OnCopy()
  3408. {
  3409. // Here I have to allocate some global memory and copy text out of my buffer
  3410. // into it, making sure I insert CR/LF combinations between lines and a
  3411. // zero byte on the end as a terminator. If the text buffer contains funny
  3412. // characters that are used to indicate font changes etc I will just leave
  3413. // them in there for the moment.
  3414. if (!OpenClipboard()) return;
  3415. if (!::EmptyClipboard()) return; // Take control of clipboard
  3416. int l1 = selStartLine;
  3417. int cp = selStartChar, size=1;
  3418. for (;;)
  3419. { while (cp!=selEndChar && textBuffer[cp]!='\n')
  3420. { cp = (cp+1)&TEXT_MASK;
  3421. size++;
  3422. }
  3423. if (cp==selEndChar) break;
  3424. size=size+2;
  3425. l1 = (l1+1)&LINE_MASK;
  3426. cp = lineBuffer[l1].address;
  3427. }
  3428. HGLOBAL h = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size);
  3429. HGLOBAL h1 = 0;
  3430. if (clipboardformat != 0)
  3431. h1 = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size);
  3432. char *p = NULL, *p1 = NULL;
  3433. if (h != NULL) p = (char *)::GlobalLock(h);
  3434. if (h1 != NULL) p1 = (char *)::GlobalLock(h1);
  3435. if (p != NULL)
  3436. { l1 = selStartLine;
  3437. cp = selStartChar;
  3438. for (;;)
  3439. { while (cp!=selEndChar && textBuffer[cp]!='\n')
  3440. { int c = textBuffer[cp];
  3441. if ((c&0xff) != CH_PROMPT && (c&0xff) != CH_ENDPROMPT) *p++ = c;
  3442. if (p1 != NULL) *p1++ = c;
  3443. cp = (cp+1)&TEXT_MASK;
  3444. size++;
  3445. }
  3446. if (cp==selEndChar) break;
  3447. *p++ = '\r'; *p++ = '\n'; // CR/LF combination at end of line
  3448. if (p1 != NULL)
  3449. { *p1++ = '\r';
  3450. *p1++ = '\n';
  3451. }
  3452. l1 = (l1+1)&LINE_MASK;
  3453. cp = lineBuffer[l1].address;
  3454. }
  3455. *p = 0;
  3456. ::GlobalUnlock(h);
  3457. if (p1 != NULL)
  3458. { *p1 = 0;
  3459. ::GlobalUnlock(h1);
  3460. ::SetClipboardData(clipboardformat, h1);
  3461. }
  3462. ::SetClipboardData(CF_TEXT, h);
  3463. }
  3464. ::CloseClipboard();
  3465. cwin_ensure_screen(FALSE);
  3466. }
  3467. void CMainWindow::OnPaste()
  3468. {
  3469. HGLOBAL clipboardInputHandle;
  3470. char *w;
  3471. ToIcaret();
  3472. caretVisible = TRUE;
  3473. cwin_ensure_screen(FALSE); // may jump screen to caret location
  3474. pageLine = caretLine;
  3475. insertMode = TRUE;
  3476. if (!OpenClipboard()) return;
  3477. // If I get a new PASTE operation before an earlier one has quite finished
  3478. // I will just abandon the end of the old one without further fuss.
  3479. if (clipboardInput != NULL) free(clipboardInput);
  3480. // If I had managed to register my own format I will use data in that format
  3481. // in preference to text. Otherwise I will just grab text.
  3482. if (clipboardformat != 0)
  3483. clipboardInputHandle = ::GetClipboardData(clipboardformat);
  3484. else clipboardInputHandle = 0;
  3485. if (clipboardInputHandle == 0)
  3486. clipboardInputHandle = ::GetClipboardData(CF_TEXT);
  3487. clipboardInput = NULL;
  3488. if (clipboardInputHandle != 0)
  3489. { w = (char *)::GlobalLock(clipboardInputHandle);
  3490. if (w != NULL)
  3491. { int n = strlen(w); // amount of data found
  3492. clipboardInput = clipboardInputP = (char *)malloc(n+1);
  3493. if (clipboardInput != NULL) strcpy(clipboardInput, w);
  3494. ::GlobalUnlock(clipboardInputHandle);
  3495. }
  3496. }
  3497. ::CloseClipboard();
  3498. if (caretChar != textLast)
  3499. { char oneLine[128];
  3500. for (;;)
  3501. { int i=0, c;
  3502. while (i<120 && clipboardInput!=NULL && (c=*clipboardInputP++)!=0)
  3503. { if (c == '\r') continue;
  3504. oneLine[i++] = c;
  3505. if (c == '\n') break; // Do pastes one line at a time
  3506. break; // do it one CHARACTER at a time...
  3507. }
  3508. InsertAtCaret(oneLine, i);
  3509. if (c == 0) break;
  3510. }
  3511. if (clipboardInput != NULL) free(clipboardInput);
  3512. clipboardInput = NULL;
  3513. }
  3514. cwin_ensure_screen(FALSE);
  3515. }
  3516. // The REINPUT request just combines a COPY and PASTE. It seems clumsy to
  3517. // have to do both as separate actions.
  3518. void CMainWindow::OnReInput()
  3519. {
  3520. OnCopy();
  3521. OnPaste();
  3522. }
  3523. void CMainWindow::OnSelectAll()
  3524. {
  3525. selFirstChar = selStartChar = textFirst;
  3526. selEndChar = textLast;
  3527. selFirstX = selStartX = 0;
  3528. selEndX = lineBuffer[lineLast].width;
  3529. selFirstLine = selStartLine = lineFirst;
  3530. selEndLine = lineLast;
  3531. HideCaret();
  3532. Invalidate();
  3533. ShowCaret();
  3534. }
  3535. void CMainWindow::OnClear()
  3536. {
  3537. CancelSelection();
  3538. caretChar = icaretChar = textFirst = textLast;
  3539. caretLine = icaretLine = lineFirst = lineLast;
  3540. caretX = icaretX = endX = 0;
  3541. xOffset = 0;
  3542. caretVisible = TRUE;
  3543. caretFontWidths = windowFonts.HCourier.across;
  3544. endFontWidths = windowFonts.HCourier.across;
  3545. if (inputLineStart>=0) inputLineStart = textLast;
  3546. pageLine = caretLine;
  3547. cwin_ensure_screen(FALSE);
  3548. }
  3549. // void CMainWindow::OnUndo()
  3550. // {
  3551. // DisplayMsg("OnUndo Text");
  3552. // }
  3553. void CMainWindow::OnRedraw()
  3554. {
  3555. HideCaret();
  3556. Invalidate();
  3557. ShowCaret();
  3558. }
  3559. // I make END reset the caret to the end of the text buffer, since that
  3560. // is an important state to be in, both with regard to input and to output.
  3561. // HOME just scrolls the window to make the top of the text visible, but does
  3562. // not re-position the caret. That is done because I do not generally expect
  3563. // people to want to insert characters at the very top of the buffer.
  3564. void CMainWindow::OnHome()
  3565. {
  3566. OnVScroll(SB_THUMBPOSITION, 0, NULL);
  3567. OnHScroll(SB_THUMBPOSITION, 0, NULL);
  3568. }
  3569. void CMainWindow::OnEnd()
  3570. {
  3571. icaretLine = caretLine = lineLast;
  3572. icaretChar = caretChar = textLast;
  3573. icaretX = caretX = endX;
  3574. caretFontWidths = endFontWidths;
  3575. selRootValid = TRUE;
  3576. OnVScroll(SB_FOR_CARET, 0, NULL);
  3577. OnHScroll(SB_FOR_CARET, 0, NULL);
  3578. caretVisible = TRUE;
  3579. cwin_ensure_screen(FALSE); // makes sure caret is properly on screen
  3580. }
  3581. void CMainWindow::ToIcaret()
  3582. {
  3583. caretLine = icaretLine;
  3584. caretChar = icaretChar;
  3585. caretX = icaretX;
  3586. caretFontWidths = icaretFontWidths;
  3587. OnVScroll(SB_FOR_CARET, 0, NULL);
  3588. OnHScroll(SB_FOR_CARET, 0, NULL);
  3589. caretVisible = TRUE;
  3590. cwin_ensure_screen(FALSE); // makes sure caret is properly on screen
  3591. }
  3592. //
  3593. // The font arrangements are as follows - at each stage I will have a
  3594. // master fixed-pitch font (typically "Courier New"). Whenever I change
  3595. // that font I locate a collection of slave fonts - Roman, Roman Italic
  3596. // and Symbol each in two sizes (one about the same as the master font and
  3597. // another about (10/12)^2 of that size, for use in sub- and super-scripts).
  3598. //
  3599. // When I change fonts I will only change the ones used for display in
  3600. // my window. The ones used when printing will be left as their original size.
  3601. // I only permit font selection to alter the font (name) for the simple
  3602. // fixed-pitch font that I use. But I still need to reconstruct printer
  3603. // fonts to allow for changes there.
  3604. void CMainWindow::OnResetFont()
  3605. {
  3606. HideCaret();
  3607. CClientDC dc(this);
  3608. // I need to find a suitable size for my default font, so I create one
  3609. // asking for "size=0" and see what windows hands back to me.
  3610. FontHeights fh;
  3611. CFont *f = MakeNewFont(&dc, &fh, ANSI_CHARSET, "Courier New",
  3612. FW_BOLD, 0, 0);
  3613. dc.SelectStockObject(SYSTEM_FONT);
  3614. delete f;
  3615. // If there is a font already set up somebody must discard it...
  3616. windowFonts.DeleteFonts();
  3617. windowFonts.ChangeFont(&dc, fh.height, FW_BOLD, "Courier New");
  3618. theApp.WriteProfileInt("MainWindow", "FontSize", fh.height);
  3619. theApp.WriteProfileString("MainWindow", "FontName", "Courier New");
  3620. theApp.WriteProfileInt("MainWindow", "FontWeight", 0);
  3621. // scroll activity may be needed here just as it is
  3622. // after some changes of window size.
  3623. OnSize(SIZE_RESTORED, clientWidth, clientHeight);
  3624. Invalidate(); // I think a total re-draw after a font change
  3625. // makes sense, and will not worry evan if the new
  3626. // font was identical to the old one, since after all
  3627. // the user prodded the OK not the CANCEL button.
  3628. ShowCaret();
  3629. }
  3630. void CMainWindow::OnFont()
  3631. {
  3632. HideCaret();
  3633. CClientDC dc(this);
  3634. LOGFONT logFont;
  3635. windowFonts.Courier->GetObject(sizeof(logFont), &logFont);
  3636. CFontDialog fontDialog(&logFont,
  3637. CF_SCREENFONTS | CF_FIXEDPITCHONLY |
  3638. CF_TTONLY |
  3639. CF_ANSIONLY | CF_INITTOLOGFONTSTRUCT);
  3640. // I will keep trying to obtain fonts from the user until I find one that
  3641. // can be properly installed. Maybe it is unkind, but if a font as selected
  3642. // from the dialog box fails to be created I just restart the dialog without
  3643. // further explanation.
  3644. for (;;)
  3645. { int w = fontDialog.DoModal();
  3646. if (w == IDCANCEL) break;
  3647. else if (w != IDOK) continue;
  3648. LOGFONT *lf = fontDialog.m_cf.lpLogFont;
  3649. // If there is a font already set up somebody must discard it...
  3650. windowFonts.DeleteFonts();
  3651. // Whatever the user does I do not want either very very small fonts or
  3652. // ones that would be so huge that their metrics could exceed 256. I apply
  3653. // rather arbitrary cut-offs here.
  3654. if (lf->lfHeight < -150) lf->lfHeight = -150;
  3655. if (lf->lfHeight <= -6);
  3656. else if (lf->lfHeight < 6) lf->lfHeight = -6;
  3657. else if (lf->lfHeight > 150) lf->lfHeight = -150;
  3658. windowFonts.ChangeFont(&dc, lf->lfHeight, lf->lfWeight, lf->lfFaceName);
  3659. theApp.WriteProfileInt("MainWindow", "FontSize", lf->lfHeight);
  3660. theApp.WriteProfileString("MainWindow", "FontName", lf->lfFaceName);
  3661. theApp.WriteProfileInt("MainWindow", "FontWeight",
  3662. lf->lfWeight==FW_BOLD ? 0 : 1);
  3663. break;
  3664. }
  3665. // scroll activity may be needed here just as it is
  3666. // after some changes of window size.
  3667. OnSize(SIZE_RESTORED, clientWidth, clientHeight);
  3668. Invalidate(); // I think a total re-draw after a font change
  3669. // makes sense, and will not worry evan if the new
  3670. // font was identical to the old one, since after all
  3671. // the user prodded the OK not the CANCEL button.
  3672. ShowCaret();
  3673. }
  3674. void FontArray::InitFont(CDC *dc, const char *name, int weight, int size)
  3675. {
  3676. // Create and install a suitable initial font - I use "Courier New"
  3677. // and ask for a default size the very first time, and this is
  3678. // achieved by passing down "Courier New" and "0".
  3679. // Because Courier tends to look rather light on
  3680. // the screen I will use a bold weight by default.
  3681. // I really hope that the default size will be such that 80 columns will fit
  3682. // neatly across the screen...
  3683. FontHeights fh;
  3684. weight = weight ? FW_NORMAL : FW_BOLD;
  3685. CFont *f = MakeNewFont(dc, &fh, ANSI_CHARSET, name,
  3686. weight, 0, size);
  3687. dc->SelectStockObject(SYSTEM_FONT);
  3688. delete f;
  3689. ChangeFont(dc, size == 0 ? fh.height : size, weight, name);
  3690. }
  3691. void FontArray::DeleteFonts()
  3692. {
  3693. delete Courier; Courier = NULL;
  3694. delete Roman; Roman = NULL;
  3695. delete Bold; Bold = NULL;
  3696. delete Italic; Italic = NULL;
  3697. delete Symbol; Symbol = NULL;
  3698. delete roman; roman = NULL;
  3699. delete bold; bold = NULL;
  3700. delete italic; italic = NULL;
  3701. delete symbol; symbol = NULL;
  3702. }
  3703. // The next function is a place-holder, I guess.
  3704. void CMainWindow::MeasureLine(int address, int *up, int *down, FontArray *fa)
  3705. {
  3706. *up = fa->HCourier.up;
  3707. *down = fa->HCourier.down;
  3708. }
  3709. void FontArray::ChangeFont(CDC *dc, int size, int weight, const char *facename)
  3710. {
  3711. strcpy(baseFace, facename);
  3712. boldFlag = weight;
  3713. Courier = MakeNewFont(dc, &HCourier, ANSI_CHARSET,
  3714. facename, weight, 0, size);
  3715. // I choose the scale factor 1.2^2 between my main and my script font mainly
  3716. // because that is what TeX seems to have adopted - Windows will generally
  3717. // round the size I ask for somewhat.... I also reload the size of my
  3718. // basic fixed-pitch font in case an inconvenient value had been handed in
  3719. // to start with and creating the font led to rounding.
  3720. size = HCourier.height;
  3721. int small = (100*size+72)/144;
  3722. // These fonts have my predefined font-names and weights. They are not in fact
  3723. // really (?) used at present.
  3724. Roman = MakeNewFont(dc, &HRoman, ANSI_CHARSET,
  3725. "Times New Roman", FW_NORMAL, 0, size);
  3726. Bold = MakeNewFont(dc, &HBold, ANSI_CHARSET,
  3727. "Times New Roman", FW_BOLD, 0, size);
  3728. Italic = MakeNewFont(dc, &HItalic, ANSI_CHARSET,
  3729. "Times New Roman", FW_NORMAL, 1, size);
  3730. Symbol = MakeNewFont(dc, &HSymbol, SYMBOL_CHARSET,
  3731. "Symbol", FW_NORMAL, 0, size);
  3732. roman = MakeNewFont(dc, &Hroman, ANSI_CHARSET,
  3733. "Times New Roman", FW_NORMAL, 0, small);
  3734. bold = MakeNewFont(dc, &Hbold, ANSI_CHARSET,
  3735. "Times New Roman", FW_BOLD, 0, small);
  3736. italic = MakeNewFont(dc, &Hitalic, ANSI_CHARSET,
  3737. "Times New Roman", FW_NORMAL, 1, small);
  3738. symbol = MakeNewFont(dc, &Hsymbol, SYMBOL_CHARSET,
  3739. "Symbol", FW_NORMAL, 0, small);
  3740. theApp.mainWindow->LineSizes();
  3741. ::DestroyCaret();
  3742. theApp.mainWindow->CreateSolidCaret(
  3743. 2*GetSystemMetrics(SM_CXBORDER), HCourier.height);
  3744. }
  3745. void CMainWindow::LineSizes()
  3746. {
  3747. int up, down, pos;
  3748. int lineFirst = theApp.mainWindow->lineFirst;
  3749. int lineLast = theApp.mainWindow->lineLast;
  3750. TextLine *lineBuffer = &theApp.mainWindow->lineBuffer[0];
  3751. MeasureLine(lineBuffer[lineFirst].address, &up, &down, &windowFonts);
  3752. lineBuffer[lineFirst].position = 0;
  3753. lineBuffer[lineFirst].up = up;
  3754. lineBuffer[lineFirst].height = up+down;
  3755. lineBuffer[lineFirst].width = 000;
  3756. pos = up + down;
  3757. for (int i=(lineFirst+1)&LINE_MASK;;i=(i+1)&LINE_MASK)
  3758. { MeasureLine(lineBuffer[i].address, &up, &down, &windowFonts);
  3759. lineBuffer[i].position = pos;
  3760. lineBuffer[i].up = up;
  3761. lineBuffer[i].height = up+down;
  3762. lineBuffer[i].width = 000;
  3763. pos += up + down;
  3764. if (i==lineLast) break;
  3765. }
  3766. }
  3767. // Now some support for a really crude and simple text-based help window.
  3768. // The interface I provide here is intended to be such that I can have
  3769. // alternative implementations (eg using Unix) that use a "curses" style
  3770. // screen manager.
  3771. // For Windows I will only support an 80 by 25 window. I guess it
  3772. // would be easy enough to permit other sizes, except that I do not have
  3773. // an easy answer to what should happen if the user re-sizes the window
  3774. // while other things are going on. Hence my conservative caution - at
  3775. // least for now!
  3776. extern "C" {
  3777. int LINES = 25, COLS = 80;
  3778. // initscr() must be called once at the start of a run
  3779. extern void initscr();
  3780. // initkb() and resetkb() delimit regions in the code where keyboard
  3781. // input is treated as requests to the curses window but is accepted
  3782. // with no delay and no echo. Also mouse events can be posted during
  3783. // this time.
  3784. extern void initkb();
  3785. extern void resetkb();
  3786. extern int mouse_button; // set non-zero when user presses a button
  3787. extern int mouse_cx; // 0 <= mouse_cx < COLS
  3788. extern int mouse_cy; // 0 <= mouse_cy < LINES
  3789. // refresh() is called to force the screen to be up to date
  3790. extern void refresh();
  3791. // endwin() hides the curses window, restoring simple text handling
  3792. extern void endwin();
  3793. // Move text insertion point. Origin (0,0) is top left of screen
  3794. extern void move(int y, int x);
  3795. // standout() and standend() delimit inverse video (or whatever) text
  3796. extern void standout();
  3797. extern void standend();
  3798. // erase() clears the whole screen
  3799. extern void erase();
  3800. //
  3801. // addch() and addstr() add text to the screen, advancing the cursor. I
  3802. // view it as illegal to write beyond either right or bottom margin of the
  3803. // screen.
  3804. //
  3805. extern void addch(int ch);
  3806. extern void addstr(char *s);
  3807. //
  3808. // getch() reads a character from the keyboard. It does not wait for
  3809. // a newline, and does not echo anything. Because the name getch() may be
  3810. // in use in some C libraries in a way that could conflict I use some
  3811. // re-naming here. If there has been a mouse-click recently then getch()
  3812. // should return a value (0x100 + bits) where the odd bits may indicate which
  3813. // button was pressed. In that case (mouse_cx,mouse_cy) will be the
  3814. // character-position coordinates at which the hit was taken. Systems
  3815. // that can not support a mouse do not have to worry about this and can always
  3816. // return a value in the range 0..255, or EOF. On some systems getch() will
  3817. // return 0 with no delay if there is no character available (so that
  3818. // the application will busy-wait). On others it is entitled to wait until
  3819. // the user presses a key. But (once again) it should not do line editing or
  3820. // wait for an ENTER.
  3821. //
  3822. extern int my_getch();
  3823. #undef getch
  3824. #define getch() my_getch()
  3825. }
  3826. static BOOL helpShown = FALSE;
  3827. static int helpChar = -1;
  3828. void initscr()
  3829. {
  3830. helpShown = FALSE;
  3831. helpChar = -1;
  3832. }
  3833. void initkb()
  3834. {
  3835. if (!helpShown)
  3836. { erase();
  3837. theApp.mainWindow->helpWindow->ShowWindow(SW_SHOW);
  3838. helpShown = TRUE;
  3839. }
  3840. helpChar = -1;
  3841. }
  3842. void resetkb()
  3843. {
  3844. if (helpShown)
  3845. { theApp.mainWindow->helpWindow->ShowWindow(SW_HIDE);
  3846. helpShown = FALSE;
  3847. }
  3848. }
  3849. void endwin()
  3850. {
  3851. resetkb();
  3852. }
  3853. static int helpY, helpX;
  3854. void move(int y, int x)
  3855. {
  3856. helpY = y;
  3857. helpX = x;
  3858. }
  3859. void erase()
  3860. {
  3861. helpX = helpY = 0;
  3862. for (int y=0; y<LINES; y++)
  3863. for (int x=0; x<COLS; x++)
  3864. theApp.mainWindow->helpWindow->contents[y][x] = ' ';
  3865. theApp.mainWindow->helpWindow->highline =
  3866. theApp.mainWindow->helpWindow->highstart =
  3867. theApp.mainWindow->helpWindow->highend = -1;
  3868. }
  3869. void standout()
  3870. {
  3871. theApp.mainWindow->helpWindow->highline = helpY;
  3872. theApp.mainWindow->helpWindow->highstart =
  3873. theApp.mainWindow->helpWindow->highend = helpX;
  3874. }
  3875. void standend()
  3876. {
  3877. if (theApp.mainWindow->helpWindow->highline != helpY)
  3878. { theApp.mainWindow->helpWindow->highline = helpY;
  3879. theApp.mainWindow->helpWindow->highstart = 0;
  3880. }
  3881. theApp.mainWindow->helpWindow->highend = helpX;
  3882. }
  3883. void addch(int ch)
  3884. {
  3885. if (helpX < COLS && helpY < LINES)
  3886. { theApp.mainWindow->helpWindow->contents[helpY][helpX] = ch;
  3887. helpX++;
  3888. }
  3889. }
  3890. void addstr(char *s)
  3891. {
  3892. while (*s != 0) addch(*s++);
  3893. }
  3894. int mouse_button = 0, mouse_cx = 0, mouse_cy = 0;
  3895. void refresh()
  3896. {
  3897. if (!helpShown) initkb();
  3898. theApp.mainWindow->helpWindow->Invalidate();
  3899. }
  3900. int my_getch()
  3901. {
  3902. while (helpChar < 0) cwin_poll_window_manager();
  3903. int ch = helpChar;
  3904. helpChar = -1;
  3905. return ch;
  3906. }
  3907. CHelpWindow::CHelpWindow()
  3908. {
  3909. char *menuName = "HelpMenu";
  3910. HINSTANCE hInst = AfxFindResourceHandle(menuName, RT_MENU);
  3911. HMENU hMenu = ::LoadMenu(hInst, menuName);
  3912. this->CreateEx(0, mainWindowClass, "Help Window",
  3913. WS_OVERLAPPED | WS_CAPTION,
  3914. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  3915. NULL, hMenu);
  3916. helpFont.CreateFont(
  3917. 0, 0, // height, width
  3918. 0, 0, // angle of escapement, base line orientation angle
  3919. FW_BOLD, 0, // weight, italic-flag
  3920. 0, 0, ANSI_CHARSET, // underline, strike-out, character-set
  3921. OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
  3922. DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE,
  3923. "Courier New");
  3924. for (int y=0;y<25;y++)
  3925. for (int x=0;x<80;x++) contents[y][x] = ' ';
  3926. highline = 5;
  3927. highstart = 7;
  3928. highend = 11;
  3929. }
  3930. BEGIN_MESSAGE_MAP(CHelpWindow, CFrameWnd)
  3931. ON_WM_PAINT()
  3932. ON_WM_CHAR()
  3933. ON_WM_KEYDOWN()
  3934. ON_WM_LBUTTONDOWN()
  3935. ON_COMMAND(IDM_REDRAW, OnRedraw)
  3936. // ON_COMMAND(IDM_COPY, OnCopy)
  3937. ON_COMMAND(IDM_CLOSE, OnClose)
  3938. END_MESSAGE_MAP()
  3939. void CHelpWindow::OnPaint()
  3940. {
  3941. CPaintDC dc(this);
  3942. dc.SelectObject(&helpFont);
  3943. for (int i = 0; i<25; i++)
  3944. { if (i == highline)
  3945. { dc.TextOut(0, i*height, contents[i], highstart);
  3946. dc.SetTextColor(theApp.mainWindow->highlightTextColour);
  3947. dc.SetBkColor(theApp.mainWindow->highlightColour);
  3948. dc.TextOut(highstart*width, i*height, &contents[i][highstart],
  3949. highend-highstart);
  3950. dc.SetTextColor(theApp.mainWindow->textColour);
  3951. dc.SetBkColor(theApp.mainWindow->windowColour);
  3952. dc.TextOut(highend*width, i*height, &contents[i][highend],
  3953. 80-highend);
  3954. }
  3955. else dc.TextOut(0, i*height, contents[i], 80);
  3956. }
  3957. dc.SelectStockObject(SYSTEM_FONT);
  3958. }
  3959. void CHelpWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags)
  3960. {
  3961. switch (ch)
  3962. {
  3963. //case ('O' & 0x1f):
  3964. // OnCopy();
  3965. // return;
  3966. case ('L' & 0x1f):
  3967. OnRedraw();
  3968. return;
  3969. default:
  3970. helpChar = ch;
  3971. return;
  3972. }
  3973. }
  3974. void CHelpWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
  3975. {
  3976. }
  3977. void CHelpWindow::OnLButtonDown(UINT nFlags, CPoint point)
  3978. {
  3979. }
  3980. //void CHelpWindow::OnCopy()
  3981. //{
  3982. // HGLOBAL hpbmi = 0; // @@@@@@@@@
  3983. // if (this->OpenClipboard())
  3984. // { ::EmptyClipboard();
  3985. // ::SetClipboardData(CF_DIB, hpbmi);
  3986. // ::CloseClipboard();
  3987. // }
  3988. //}
  3989. void CHelpWindow::OnRedraw()
  3990. {
  3991. Invalidate();
  3992. }
  3993. void CHelpWindow::OnClose()
  3994. {
  3995. helpChar = 'q';
  3996. }
  3997. // end of c_text.cpp