home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / tvision / newed / newedit.doc < prev    next >
Encoding:
Text File  |  1993-10-31  |  44.4 KB  |  1,141 lines

  1. INTRODUCTION
  2. ------------
  3.  
  4. I originally started this project to add a wordwrap feature to
  5. the Turbo Vision EDITORS unit.  To make matters worse, I'm new
  6. to Pascal, and trying to learn the language, Turbo
  7. Vision, and event driven programming was just a bit of a
  8. hurdle.  So I asked for help, LOTS of help, and the next thing
  9. I knew I was working with people I've never met trying to
  10. create a full-blown IDE compatible wordprocessor, with the
  11. wordwrap!
  12.  
  13. Well, it got done, minus the IDE select text features such as
  14. read/write/indent blocks, etc.  If nothing else, it was one way
  15. of learning three things at once.  We all had a lot of fun,
  16. probably at my expense <grin>.
  17.  
  18. Since that time, I have gone on to other projects, and find that
  19. I no longer have the time to support NEWEDIT.  What you've got is
  20. pretty much all you're going to get, until I can find the time to
  21. add new features and/or fix any existing bugs.
  22.  
  23. NEWEDIT.PAS is a complete replacement for the original EDITORS
  24. unit.  It's major contribution is the implementation of a
  25. wordwrap feature.  It also has several other features, which
  26. will be outlined below.
  27.  
  28. If you decide to use the NEWEDIT unit with your applications,
  29. be sure you change your use clause from "uses Editors" to "uses
  30. NewEdit" in all code modules that require the editor.  Those
  31. modules included in the NEWEDIT.ZIP file already do this.
  32.  
  33. You should NOT overwrite your original EDITORS.PAS file!  Keep
  34. it for reference should you decide to dig into the work I have
  35. done and add your own features.  Comparison is a learning tool!
  36.  
  37. The rest of this document deals with the modifications I have
  38. made, discussing how they work, and whatever quirks you should
  39. be familiar with.  A command summary is also provided of all
  40. the NEWEDIT options.  Last of all, a brief discussion is given
  41. that explains how to implement your own customized key
  42. commands.
  43.  
  44. Please note that this document is NOT written for users.  It is
  45. written for you, the programmer.  A lot of the information
  46. contained herein would severely confuse your users due to its
  47. technical nature.  Don't make life harder on them by cutting
  48. and pasting the current text to make a quick users guide.  Take
  49. the time and effort to write your own, and make it as simple as
  50. possible.
  51.  
  52.  
  53.  
  54. HOW TO USE IT
  55. -------------
  56.  
  57. I figured I'd put this right up front so you can jump right into it.
  58. You can use the program either as is, or you can yank out NEWEDIT and
  59. simply patch it into your application.  I'll discuss both methods.
  60.  
  61.  
  62.  
  63. Using NEDEMO
  64. ------------
  65.  
  66. Your best bet is to create a directory and dump all the NEWEDIT.ZIP files
  67. into it.  Baring that, put the files wherever you want.
  68.  
  69. Next, ensure that all the units called in NEDEMO.PAS are available for
  70. compilation.  Now, compile the NEDEMO.PAS source.  All the other units
  71. will compile.  If you get an error message stating that a unit can't be
  72. compiled, fix your Options|Directories so the path to the unit is
  73. correct.  Or, take the unit TPU or PAS file and put it in your
  74. compilation directory.
  75.  
  76.  
  77.  
  78. Plug Into Your Application
  79. --------------------------
  80.  
  81. The NEWEDIT unit is plug-in compatible with the standard EDITORS unit.
  82. If you're not interested in the NEDEMO program, but want to use the
  83. NEWEDIT unit as is, you can easily patch it into your application.
  84.  
  85. You will need the EDITPKG.PAS and NEWEDIT.PAS programs.  NEWEDIT.PAS is
  86. the actual editor.  EDITPKG.PAS contains all the dialogs and calls to
  87. the clipboard and editor.
  88.  
  89.  
  90. 1)  In your main application INIT constructor, insert this line BEFORE
  91.     you use "TApplication.Init":
  92.  
  93.         EditPkg.Initialize_The_Editor;
  94.  
  95.     Then load any previous desktop and disable any editor commands you
  96.     don't want the user to have immediate access to.
  97.  
  98.     Last of all, initialize the clipboard with this code:
  99.  
  100.         EditPkg.Initialize_The_Clipboard;
  101.  
  102.     Look at the T_EditDemo.Init code in NEDEMO.PAS if you have
  103.     questions.
  104.  
  105. 2)  In your main application HandleEvent code, insert this line for
  106.     opening new files:
  107.  
  108.         cmNew          : EditPkg.Open_Editor ('', True);
  109.  
  110. 3)  In your main application HandleEvent code, insert this line for
  111.     opening existing disk files:
  112.  
  113.         cmOpen         : EditPkg.Run_The_Editor;
  114.  
  115. 4)  If you write the desktop to disk, for any reason, be sure you
  116.     deallocate and reallocate the clipboard.  Failure to do so will
  117.     cause your desktop file to start growing and growing!  Insert this
  118.     code around your save desktop code:
  119.  
  120.  
  121.       EditPkg.Deallocate_The_Clipboard;
  122.  
  123.       {Your code goes here!}
  124.  
  125.       EditPkg.Initialize_The_Clipboard;
  126.  
  127.  
  128.     If you have any questions on this, look in the NEDEMO.PAS
  129.     T_EditDemo.Save_Desktop function for how it should be done.
  130.  
  131. 5)  In your application's DONE destructor, be sure you deallocate the
  132.     editor buffer AFTER "TApplication.Done":
  133.  
  134.       TApplication.Done;
  135.       EditPkg.Deallocate_The_Editor;
  136.  
  137. 6)  Check all units to ensure that "uses EDITORS" is changed to "uses
  138.     NEWEDIT".
  139.  
  140. 7)  If you want to use my help system, the EDITHELP.TXT file will
  141.     probably have to be revised to suit your hc???? constant naming
  142.     convention.   That, or you'll have to write your own.
  143.  
  144.  
  145. It's not as difficult as it sounds.  I've done it with a minimum of
  146. fuss, and several of the beta testers have done the same thing.
  147.  
  148.  
  149.  
  150. COMPILER SETTINGS
  151. -----------------
  152.  
  153. If you have difficulty compiling, these are my default compiler
  154. settings:
  155.  
  156. {$A+,B-,D+,E+,F+,G+,I+,L+,N-,O+,P-,Q+,R+,S+,T-,V+,X+,Y+}
  157. {$M 16384,0,655360}
  158.  
  159.  
  160.  
  161. CREATING HELP
  162. -------------
  163.  
  164. The file EDITHELP.TXT contains the source for the context sensitive help
  165. system.  You must compile it with the TVHC.EXE program to make it
  166. functional.  Included with NED103 is a MAKEHELP.BAT file.  Simply type
  167. MAKEHELP to generate a usable help file.  Ensure that TVHC is in your
  168. directory or path.
  169.  
  170. I've done things a bit different than normal here, so some explanation
  171. might be in order.
  172.  
  173. All cm???? and hc???? constants are in the CMDFILE.PAS file. Seeing as
  174. I've mixed these constants, I didn't want CMDFILE.PAS to be overwritten
  175. by the TVHC program.  What I do in the batch file is to let TVHC
  176. generate an EDITHELP.PAS, EDITHELP.TPU, and EDITHELP.HLP file.  I then
  177. rename the EDITHELP.HLP file to NEDEMO.HLP and erase the EDITHELP.TPU
  178. file.  I keep the EDITHELP.PAS file as a handy alphabetical reference.
  179.  
  180. So, the short of it is that NEDEMO requires that the CMDFILE unit be
  181. available and that the help file be named NEDEMO.HLP.
  182.  
  183.  
  184.  
  185. CODING STYLE
  186. ------------
  187.  
  188. Every one has their way of doing things, and I'm as guilty as the next
  189. person.  The only reason I bring it up is that coding style may look
  190. very foreign to those who do it Borland's way.  This is not a
  191. justification, just an explanation!
  192.  
  193. I code the way I do so that I can quickly convert code between C,
  194. PASCAL, and Ada.
  195.  
  196. I don't put all of my if/elses on one line.  I like to see which
  197. condition is met when I'm debugging.
  198.  
  199. I use lots of white space so things don't look as bad as they really
  200. are <grin>.
  201.  
  202. I use 43/50 column mode and print in compressed mode, therefore my code
  203. does tend to go beyond the right margin.
  204.  
  205. Last of all, and this is probably important to some of you, I use fully
  206. qualified variable and procedure/function names.  I HATE having to
  207. figure out where a variable or procedure/function is.  Where I work,
  208. people are summarily shot for failing to do so!
  209.  
  210.  
  211.  
  212. WHEN IS A BUG NOT A BUG?
  213. ------------------------
  214.  
  215. When it's a feature, of course!  I've seen several so-called bug
  216. reports describing what the authors consider to be bugs in the
  217. EDITORS unit, when in fact they are reporting features.
  218. Granted, there are some bugs there, and the service these
  219. authors provide by reporting them is invaluable.
  220.  
  221. To set the record straight, the following two items are NOT
  222. bugs!
  223.  
  224. 1)  Pressing ^T will in some instances delete more than just the
  225.     word you want deleted.
  226.  
  227.     Sorry, this is NOT a bug!  The Editors unit defines a
  228.     particular character set.  Any character not in that
  229.     character set is a "non-character".  ^T works like this:
  230.  
  231.     a)  Delete from the cursor position all characters to the
  232.         first character not defined in our character set.  That
  233.         takes care of the current word.
  234.  
  235.     b)  Now we need to delete all characters NOT in the
  236.         character set to the next character that IS in the
  237.         character set.  That deletes the "blank space" between
  238.         words.
  239.  
  240.     Step B is what most people consider the "bug" for you will
  241.     delete characters that are not in the character set between
  242.     words.  It's the way the Editor was designed!  You may not
  243.     like it, but that doesn't make it a bug!
  244.  
  245.     This supposed "bug" is not a problem with NEWEDIT.  In the
  246.     process of changing the character set to acoomodate
  247.     wordwrap, it has become a non-issue.  I just wanted users to
  248.     be aware of the difference in NEWEDIT and EDITORS.
  249.  
  250. 2)  Selecting text and then pressing a key deletes selected
  251.     text.
  252.  
  253.     This is a feature that allows you to quickly do a FIND and
  254.     then type in new text to replace old text.  Yes Virginia,
  255.     you should use SEARCH/REPLACE, but there are times when this
  256.     little feature used in conjunction with FIND is invaluable.
  257.     It also allows you to mark text for quick selection and
  258.     deleting.
  259.  
  260.     Several supposed "fixes" for this feature will do nothing
  261.     but clobber your FIND and/or SEARCH/REPLACE capabilities.
  262.  
  263.     So what if you accidently erase the selected text -- ^Undo
  264.     it!
  265.  
  266.     I have deliberately retained this feature in NEWEDIT.
  267.     Please do not report to me that FIND and/or SEARCH/REPLACE
  268.     no longer work because you tried to incorporate one of the
  269.     "bug fixes" for this alleged problem!
  270.  
  271.  
  272.  
  273. 43/50 LINE VIDEO
  274. ----------------
  275.  
  276. Have added the capability of using 43/50 line screens if you have EGA or
  277. better.  The option can be found in the OPTIONS menu.  It is
  278. automatically disabled if the user doesn't have the capability.
  279.  
  280. Note that Load/Store remembers what video mode you were in and resets
  281. the program to that mode when the desktop is automatically loaded.
  282.  
  283.  
  284.  
  285. AUTOINDENT
  286. ----------
  287.  
  288. The AutoIndent command has been changed from ^O to ^QI.  I did
  289. this for two reasons.  The first is that ^O is right next to
  290. ^I, the tab key.  The other is that ^QI is the WordStar
  291. sequence for this feature.  It keeps the user out of trouble,
  292. and it makes the editor conform more to WordStar, which I
  293. presume was Borland's original intent.
  294.  
  295.  
  296.  
  297. CENTERING TEXT
  298. --------------
  299.  
  300. A line of text may be centered by using ^OC.  Centering is
  301. based on the setting of the Right_Margin.  If a line is blank,
  302. or the length of the line exceeds the Right_Margin value,
  303. nothing will happen.  Spaces at the beginning of lines are not
  304. used when calculating the line length.
  305.  
  306. The cursor will be moved to the end of the line after it has
  307. been centered.
  308.  
  309.  
  310.  
  311. CHARACTER SET
  312. -------------
  313.  
  314. The original EDITORS unit had the following character set:
  315.  
  316.   WordChars: set of Char = ['0'..'9', 'A'..'Z', '_', 'a'..'z'];
  317.  
  318. This implementation of the NEWEDIT unit has changed the
  319. character set to be to the following:
  320.  
  321.   WordChars: set of Char = ['!'..#255];
  322.  
  323. This was done to allow simpler coding for the wordwrap feature.
  324. Note that the tab character is no longer considered a valid
  325. character.  This is described in further detail later in the
  326. document.  Also note that jumping from word to word will no
  327. longer bypass extended ASCII characters.
  328.  
  329.  
  330.  
  331. COLORS
  332. ------
  333.  
  334. To be quite honest, the whole GetPalette color business is
  335. beyond me and the TV documentation, while going into depth on
  336. the subject, doesn't lend to clarifying the issue.  So, I've
  337. done a quick and dirty change of the whole blasted palette.
  338.  
  339. NEDEMO.PAS has a constant called New_Colors.  It contains a
  340. whole new color scheme.  As a programmer, you have several
  341. options available to you.
  342.  
  343. You can keep things like I've got them.
  344.  
  345. You can change them back to the TV defaults by deleting the
  346. New_Colors constant and the T_EditDemo.GetPalette procedure in
  347. its entirety.
  348.  
  349. You can open up APP.PAS and replace the CColor constant data
  350. with the New_Colors data to make ALL your applications have
  351. these new colors.  Just make sure you delete New_Colors and
  352. T_EditDemo.GetPalette so you don't duplicate things.
  353.  
  354. You can change New_Colors to use your own color scheme.
  355.  
  356. Play around and see what you would prefer.
  357.  
  358. The ASCII file COLORS.ASC is being distributed with NEWEDIT.ZIP
  359. to show you how to change the color palette.  It was originally
  360. written by CIS member Steve Shafer, 71121,1771, and is available with
  361. NED103 by his permission.
  362.  
  363.  
  364.  
  365. CONSTANTS
  366. ---------
  367.  
  368. I've put all of the NEWEDIT constants that support the new features in
  369. the 200 range.  I did this to allow programmers to enable/disable these
  370. commands in any menu system they design.
  371.  
  372.  
  373.  
  374. CTRL-BACKSPACE
  375. --------------
  376.  
  377. Added this feature to allow two options for deleting text from
  378. the cursor to the left margin.  You can use ^BackSpace or ^QH.
  379.  
  380.  
  381.  
  382. CTRL-HOME AND CTRL-END
  383. ----------------------
  384.  
  385. To add compatibility with the regular IDE editor, I have added
  386. the ability to jump to the top and bottom of a page.
  387. [Ctrl]-[Home] will take the cursor to the top of a page and
  388. [Ctrl]-[End] will take the cursor to the bottom of a page.
  389.  
  390. Note that the cursor does NOT go to the first position in the
  391. line. The actual position it ends up at is based on the current
  392. position of the cursor on the current line when one of these
  393. key sequences are pressed, and the length of the new line it
  394. ends up on.
  395.  
  396.  
  397.  
  398. DESKTOP MENU
  399. ------------
  400.  
  401. The user should have a menu that allows them to load and store
  402. the desktop, so I added a DESKTOP menu.  I've also added the
  403. 43/50 line mode video toggle to this menu.
  404.  
  405.  
  406.  
  407. DOCUMENT REFORMAT
  408. -----------------
  409.  
  410. Pressing ^QU will allow you to reformat a document from either
  411. the current line, or the beginning of a document, to the end of
  412. the document.  A dialog box appears with radio buttons asking
  413. you where you want to start reformatting.
  414.  
  415. You must be VERY careful when using this option.  It is based
  416. on the paragraph reformat feature.  You will note in that
  417. section that reformatting is based on the indentation of the
  418. first line in a paragraph, and whether or not the AutoIndent
  419. feature is toggled on.
  420.  
  421. Your screen will scroll to follow the reformat process. This
  422. allows the user to know something is happening, instead of just
  423. having the system lock up while text is reformatted.
  424.  
  425.  
  426.  
  427. END OF LINE SPACES
  428. ------------------
  429.  
  430. Spaces at the end of lines are automatically removed.  This was
  431. required to make the wordwrap and reformat feature work
  432. properly.  Spaces on blank lines are also removed.  Only the
  433. CR/LF is kept.
  434.  
  435. Spaces are NOT removed until the cursor has traveled over or
  436. onto a line.  Therefore, if you load a file created by another
  437. ASCII editor that does not strip spaces, they will remain in
  438. the document until you move through the document with the
  439. cursor keys.
  440.  
  441.  
  442.  
  443. INDICATOR LINE
  444. --------------
  445.  
  446. The window TIndicator line has been modified.  Instead of that
  447. stupid character that pops up to indicate the document has been
  448. modified, an "M" now appears.  Also, an "I" appears when
  449. AutoIndent is active, and a "W" pops up when wordwrap is on.
  450. This means I had to take some space away from the cursor stats,
  451. but, with only 64K available, I don't think there will be a problem
  452. unless the user is entering some weird text.
  453.  
  454.  
  455.  
  456. INSERT NEW LINE
  457. ---------------
  458.  
  459. Pressing ^N will insert a CR/LF pair at the current cursor
  460. position.  All characters AT the cursor and to the end of the
  461. line will be moved down to the next line.  The difference
  462. between pressing ^N and [Enter] is that with ^N the cursor
  463. stays at its current position and does NOT move down to the
  464. next line.  The only exception to this would be if the cursor
  465. was at the end of a line that has its spaces removed when ^N is
  466. pressed.  In that case, the cursor goes to the new end of line.
  467.  
  468. This feature was added to allow further compatibility with the
  469. current IDE.
  470.  
  471.  
  472.  
  473. JUMP TO LINE NUMBER
  474. -------------------
  475.  
  476. Pressing ^JL will allow the user to jump to a particular line
  477. number.  A dialog box will appear that requests the user input
  478. a line number to jump to.  Valid entries range from 1 to 9999.
  479. I believe 9999 is a high enough range.  If someone thinks it
  480. should be larger, please let me know.
  481.  
  482. The dialog box differs from the right margin and preset tab
  483. dialogs in that the initial Line_Number value is always blank.
  484. However, once the user inputs a value it is remembered, thereby
  485. allowing the user to jump to the same line number after they
  486. scroll around the text.
  487.  
  488.  
  489.  
  490. LOAD/STORE OPERATIONS
  491. ---------------------
  492.  
  493. The original load/store bug in EDITORS.PAS has been fixed.
  494. In addition, new Read/Write streams have been incorporated
  495. in TEditor.Load and TEditor.Store to allow saving of the new
  496. variables in the TEditor object.
  497.  
  498.  
  499.  
  500. MEMORY
  501. ------
  502.  
  503. In the original Borland EDITORS unit, you are allowed to define how much
  504. of an edit buffer you can set aside on the heap.  This buffer size
  505. determines how many editor windows you can have open at one time.  The
  506. default in the EDITORS unit was to take the buffer size the programmer
  507. wanted, and to then allocate all available heap to the editors, and
  508. leave the editors buffer size as the free space.  So, you got lots of
  509. memory so you could open up lots of editor windows.
  510.  
  511. Not everyone wants all that heap set aside for the editors.  The EDITPKG
  512. unit maintains the Borland default, but by changing two lines in the
  513. EDITPKG.Initialize_The_Editor procedure you can set aside exactly how
  514. much buffer space you want for the editor, and ONLY that buffer space,
  515. leaving the rest of the heap available to your applications.  Just search
  516. for the label "HEAP" to find these two lines.
  517.  
  518.  
  519.  
  520. MENUS
  521. -----
  522.  
  523. How to create menus is pretty much covered in the TurboVision
  524. documentation, and I'm not going to cover it here.  What I would
  525. like to state is that I've provided ready-to-use code in the
  526. NEWEDIT unit and the NEDEMO program that will allow you to
  527. toggle your menu options on/off.  All you need to do is
  528. uncomment the code I have in place to make use of toggling your
  529. new menu options on/off.
  530.  
  531. The T_EditDemo.Init constructor in NEDEMO contains the original
  532. disabling code.
  533.  
  534. The TFileEditor.UpdateCommands procedure in NEWEDIT contains the
  535. actual updating code.
  536.  
  537. WARNING!!!!  You will pay a HEAVY price for each additional
  538.              SetCmdState (Command, True) line you uncomment in
  539.              procedure TfileEditor.UpdateCommands.  The price is
  540.              in system response time.  Uncommenting all the
  541.              lines will turn your fast 64K editor into a slow
  542.              clunky piece of junk!  Of course, you don't really
  543.              need to disable/enable menu options for editor
  544.              menus you might design.  My advice is to either
  545.              keep the menu minimal and uncomment only what you
  546.              really need, or leave all your editor menu options
  547.              enabled at all times.
  548.  
  549.  
  550.  
  551. PARAGRAPH REFORMAT
  552. ------------------
  553.  
  554. This feature is available to the user via the ^B key.  It works
  555. regardless of whether or not wordwrap is active.  Reformatting
  556. is based on the current right margin position.  As with
  557. wordwrap, reformatting is not possible if the cursor is beyond
  558. the right margin position.  An error dialog will appear.
  559.  
  560. Paragraph reformat also works with the AutoIndent feature.
  561. However, it is important to note that the reformat process is
  562. based on the current line the cursor happens to be sitting on
  563. when ^B is pressed.  Therefore, if you use hanging paragraphs
  564. (a paragraph where the first line sticks out further to the
  565. left margin than the rest of the paragraph) you need to be
  566. careful.  If the cursor is on the first line of the paragraph,
  567. the reformatting takes place according to that line's left
  568. margin setting.  This is a feature, not a bug.  Try the same
  569. thing with MicroStar or SideKick and you'll see what I mean.
  570.  
  571. If AutoIndent is on, and the paragraph is so indented that
  572. reformatting encounters a word that will not fit on the line
  573. according to the left and right margin settings, an error
  574. dialog will appear.  However, any reformatting accomplished
  575. prior to this situation will have taken effect.  The cursor
  576. should stop on the word it couldn't wrap.  This should preclude
  577. the reformatting process locking up the system because it can't
  578. wrap a word that is too long.
  579.  
  580. The cursor will end up at the first blank line after the
  581. paragraph when you press ^B.  Therefore, you can reformat
  582. paragraphs in quick succession.
  583.  
  584. Note that unlike some other editors, this implementation of
  585. paragraph reformatting will retain double spaces after periods,
  586. exclamation points, question marks, and colons.  If you are in
  587. the habit of only using one space, it's not syntactically
  588. correct.  Two spaces will be inserted instead, providing the
  589. cursor was sitting on one of these punctuation marks when the
  590. reformatting process was doing its thing.  Otherwise, no change
  591. will be made to your original text.
  592.  
  593. I've noticed that if the editor is almost filled to capacity
  594. and the user tries to do a ^B on one of the last paragraphs,
  595. the reformatting doesn't work properly.  I have no idea what to
  596. do about this, as I suspect it has something to do with buffers
  597. and all.
  598.  
  599.  
  600.  
  601. PLACE MARKERS
  602. -------------
  603.  
  604. The editor now allows the user to set place markers within the
  605. document.  ^K#, where # is a value between 1 and 0 on the
  606. keyboard, allows the user to set up to 10 place markers.  The
  607. user can jump to a place marker by pressing ^Q#, where # is a
  608. value between 1 and 0 on the keyboard.
  609.  
  610. Place markers are not visible, but they are there
  611. none-the-less.  They are maintained in an array called
  612. Place_Marker, which is contained in the TEditor object.
  613.  
  614. Place markers are constantly updated as the user inserts and
  615. deletes text.  If a place marker is contained within text that
  616. is deleted, it is set back to zero.  The cursor key goes
  617. nowhere if it encounters a place marker with a zero value as a
  618. result of a ^Q# request.  Of course, this also means you can
  619. not set a place marker in column 1 of line 1, for it is the 0th
  620. position in the document.  Use [Ctrl]-[PgUp] instead!
  621.  
  622.  
  623.  
  624. PRESET TABS
  625. -----------
  626.  
  627. A preset tab feature has been implemented, allowing the user to
  628. define up to 74 tab stops.  The ^OI keys will bring up a tab
  629. stop dialog box.  The box has a ruler across the top and an
  630. input line beneath the ruler.
  631.  
  632. To set tabs, the user should place a character in the input
  633. line directly beneath the ruler setting where a tab is wanted.
  634. The input line is a normal TInputLine, therefore its behavior
  635. follows the norm.  Note that if the input line is filled with
  636. spaces the user will not be able to enter any data.  To help
  637. alleviate this situation, the TInputLine data is stripped of
  638. all unnecessary spaces after the last tab stop every time the
  639. user exits the dialog box.
  640.  
  641. The data is placed in a string variable called Tab_Settings
  642. which is contained in the TEditor object.
  643.  
  644. Tab_Settings is automatically initialized to increments of 7.
  645. The initialization takes place in TEditor.Init.
  646.  
  647. Pressing [Esc] or the OK dialog button will keep the current
  648. Tab_Settings.
  649.  
  650.  
  651.  
  652. RIGHT MARGIN
  653. ------------
  654.  
  655. The user can set the right margin of the document.  This is
  656. done by pressing the ^OR sequence.  A dialog box will appear
  657. asking the user to enter a right margin value.  The current
  658. value will be shown each time the dialog comes up.  The default
  659. value is 76.  The programmer must change this to something else
  660. in the TEditor.Init code if they want a different value.  The
  661. user has only three options; enter the value, press the OK
  662. button, or press [Esc].  Pressing OK or [Esc] will keep the
  663. current value.  Valid entries are 10 through 255.  If the user
  664. enters a value that is not within this range, the current value
  665. is maintained.  No error message will be given!
  666.  
  667. The right margin value is contained in the TEditor object.  The
  668. variable is called Right_Margin.
  669.  
  670.  
  671.  
  672. SAVE FEATURES
  673. -------------
  674.  
  675. There are several more save features than normal.  I've tried
  676. to implement the most important WordStar commands that apply to
  677. saving the document.  I've also retained Borland's current
  678. features.
  679.  
  680.  
  681.   [F2] or ^KS - cmSave     - Save text to current file and
  682.                              resume editing.
  683.   ^KF         - cmSaveAs   - Save text to another file.
  684.   ^KD or ^KX  - cmSaveDone - Save text to current file and exit
  685.                              editor.
  686.  
  687. Note that where cmSaveAs used to be accessible only through a
  688. menu option like "Save As...", it is now accessible to the
  689. user, regardless of the programmers menu system, via the ^KF.
  690. This applies to all the save features.  They are all accessible
  691. to the user.
  692.  
  693.  
  694.  
  695. SCROLL DOWN
  696. -----------
  697.  
  698. Pressing ^Z emulates the IDE function of scrolling the screen
  699. up while maintaining the cursor position.  If the original
  700. cursor position scrolls off the screen, the cursor scrolls down
  701. a line.  This forces the cursor to stay in the upper left
  702. corner of the screen should the original cursor position scroll
  703. away.
  704.  
  705.  
  706.  
  707. SCROLL UP
  708. ---------
  709.  
  710. Pressing ^W emulates the IDE function of scrolling the screen
  711. down while maintaining the cursor position.  If the original
  712. cursor position scrolls off the screen, the cursor scrolls up a
  713. line.  This forces the cursor to stay in the lower left corner
  714. of the screen should the original cursor position scroll away.
  715.  
  716.  
  717.  
  718. SELECT WORD
  719. -----------
  720.  
  721. Pressing ^KT will highlight the current word the cursor is sitting on.
  722. This function was added to maintain compatibility with the IDE.
  723.  
  724. The word is marked from the current cursor position to the next space or
  725. end of line, whichever comes first.  The marked word is put into the
  726. clipboard without further effort on the users part.  Marking works in
  727. the normal manner, i.e., it disappears the moment you move the cursor.
  728.  
  729. Note that NOTHING will happen if you try to use this command when the
  730. cursor is sitting on a space or the end of a line.
  731.  
  732.  
  733.  
  734. TABS
  735. ----
  736.  
  737. I realize that Borland allows the tab (#9) character in the
  738. original editors package.  They probably did this to allow a
  739. maximal amount of data to fit in a small 64K buffer.  However,
  740. the current implementation of this NEWEDIT unit does not allow
  741. tabs.  I did this to make the added features a simpler exercise
  742. in coding.
  743.  
  744. Tab works in two different ways.
  745.  
  746. When in INSERT mode, the cursor will attempt to go to the next
  747. tab stop, happily INSERTING SPACES as it looks for that next
  748. stopping point.  If it is at the last tab stop, it stops
  749. inserting spaces and takes a big jump to the first character of
  750. the next line.  Try to keep tab use to a minimum when the
  751. cursor is in insert mode.  You eat up valuable buffer space
  752. real quick!
  753.  
  754. When in OVERSTRIKE mode, the cursor will attempt to go to the
  755. next tab stop, happily skipping over characters as it looks for
  756. that next stopping point.  If it is at the last tab stop, OR
  757. the line is too short to allow it to reach the next tab stop,
  758. it takes a big jump to the first character of the next line.
  759.  
  760. I think you'll see that if you set your tabs right, you can
  761. quickly indent a whole paragraph deeper than it already is.
  762. Also, you don't come to a screeching halt.  If in OVERSTRIKE
  763. mode you can move through a document real quick.
  764.  
  765.  
  766.  
  767. TVFORMS DEMO AND PDEMO OBJECT
  768. -----------------------------
  769.  
  770. So, you want wordwrap in your PMemo object?  No problem!
  771. I'll use the GENPARTS.PAS program for the TVFORMS demo program
  772. as an example.  Find the line in the function "MakeForm" that
  773. looks like this:
  774.  
  775.      C := New (PMemo, Init (R, nil, PScrollBar(C), nil, DescrLen));
  776.  
  777. Now, add these two lines right after it:
  778.  
  779.      PMemo(C)^.Word_Wrap := TRUE;
  780.      PMemo(C)^.Right_Margin := R.B.X - R.A.X;
  781.  
  782. Now you've got wordwrap!  You simply type cast the Wordwrap
  783. variable to default to TRUE and Right_Margin for where you want
  784. wordwrap to occur, and that's all there is to it!  Just
  785. remember to do this for each and every PMemo you want wordwrap
  786. for.
  787.  
  788. NOTE:  The editor has an undo buffer.  As you type, each
  789.        character is placed in that buffer so you can undo a
  790.        mistake.  The buffer is part of the memory you request
  791.        when allocating a TMemo.
  792.  
  793.        What this means is that you might encounter an out of
  794.        memory condition before you actually use all of the
  795.        buffer.  If you do not default to wordwrap, and then type
  796.        until you run out of memory, attempting to reformat your
  797.        text will most likely reformat only one or two lines. This
  798.        is because the wordwrap feature tries to throw in a CR/LF
  799.        pair, and doesn't succeed.  Simply move the cursor to the
  800.        left and back to the right to clear your undo buffer and
  801.        then try to wrap the text.  If you still can't wrap, you're
  802.        simply out of memory and have to cut down on your text.
  803.  
  804.        Your editor documentation, which is supplied on your
  805.        original compiler diskettes, discusses this anomaly at
  806.        length.  I don't like it, but I'd rather not muck with
  807.        the undo buffer at this time.
  808.  
  809.  
  810.  
  811. UNDO
  812. ----
  813.  
  814. ^U is the normal undo feature for the NEWEDIT unit.  However,
  815. to maintain compatibility with the IDE editor, I have also
  816. provided a ^QL option.  They both do the same job.
  817.  
  818.  
  819.  
  820. WORDWRAP
  821. --------
  822.  
  823. Wordwrap is toggled on/off via the ^OW sequence.  When wordwrap
  824. is on, a "W" appears on the status line.  Wordwrap is fast, and
  825. should keep up with the most fluid typist.  There should be no
  826. "jerkiness" in the wrap effect.
  827.  
  828. Wordwrap is based on the "push the line out to the right until
  829. the cursor reaches the current right margin setting" principle.
  830. Therefore, if you are on a line and inserting text, the text
  831. will be pushed beyond the right margin until the cursor gets
  832. there, at which point the text beyond the cursor is wrapped.
  833.  
  834. The right margin is considered to be the current right margin
  835. value + 1.  For example, if your right margin is set to 69,
  836. then the cursor CAN go to column 70.  Trying to exceed column
  837. 70 will cause wordwrap to occur.
  838.  
  839. Wordwrap will not work if the cursor is beyond the current
  840. right margin setting.  In this event, an error dialog box will
  841. appear.
  842.  
  843. Wordwrap will also work with the AutoIndent mode turned on.  In
  844. this case, the cursor goes to the same first character position
  845. on the new line as the line it was on when wrap occurred.
  846.  
  847. If you try to type a blank line of spaces to the right margin,
  848. wordwrap will remove the spaces and insert just a CR/LF pair.
  849. Wordwrap will also bring up an error message if you try to type
  850. in an entire line out to the right margin without spaces.
  851.  
  852.  
  853.  
  854. COMMAND SUMMARY
  855. ---------------
  856.  
  857. The following list contains all the commands available in this
  858. implementation of the Turbo Vision NEWEDIT unit.
  859.  
  860. Command                TV Command     Description
  861. --------------------   -------------  ------------------------------------------------
  862.  
  863.           [Ctrl]-[A] - cmWordLeft     Move cursor left to previous word.
  864.           [Ctrl]-[B] - cmReformPara   Reformat paragraph to current margins.
  865.           [Ctrl]-[C] - cmPageDown     Page down one screen.
  866.           [Ctrl]-[D] - cmCharRight    Move cursor right one character.
  867.           [Ctrl]-[E] - cmLineUp       Move cursor up one line.
  868.           [Ctrl]-[F] - cmWordRight    Move cursor right to next word.
  869.           [Ctrl]-[G] - cmDelChar      Delete character cursor is on.
  870.           [Ctrl]-[H] - cmBackSpace    Delete character to left of cursor.
  871.           [Ctrl]-[I] - cmTabKey       Move right to next tab position.
  872.       [Ctrl]-[J]-[L] - cmJumpLine     Jump to a line number.
  873. *     [Ctrl]-[K]-[#] - cmSetMark0-9   Set place marker (0 to 9).
  874.       [Ctrl]-[K]-[B] - cmStartSelect  Begin selecting text.
  875.       [Ctrl]-[K]-[C] - cmPaste        Copy text from clipboard into document.
  876.       [Ctrl]-[K]-[D] - cmSaveDone     Save document and exit editor immediately.
  877.       [Ctrl]-[K]-[F] - cmSaveAs       Save current document to another file.
  878.       [Ctrl]-[K]-[H] - cmHideSelect   Hide selected text.
  879.       [Ctrl]-[K]-[K] - cmCopy         Copy text from document into clipboard.
  880.       [Ctrl]-[K]-[S] - cmSave         Save document and continue editing.
  881.       [Ctrl]-[K]-[T] - cmSelectWord   Select current word cursor is on.
  882.       [Ctrl]-[K]-[Y] - cmCut          Remove text from document into clipboard.
  883.       [Ctrl]-[K]-[X] - cmSaveDone     Save document and exit editor immediately.
  884.           [Ctrl]-[L] - cmSearchAgain  Continue searching for specified text.
  885.           [Ctrl]-[M] - cmNewLine      Enter CR/LF pair.
  886.           [Ctrl]-[N] - cmInsertLine   Add CR/LF pair AT cursor and keep cursor position.
  887.       [Ctrl]-[O]-[C] - cmCenterText   Center text on cursor line.
  888.       [Ctrl]-[O]-[I] - cmSetTabs      Set tab positions.
  889.       [Ctrl]-[O]-[R] - cmRightMargin  Set right margin position of line.
  890.       [Ctrl]-[O]-[W] - cmWordWrap     Toggle word wrap mode on or off.
  891. *     [Ctrl]-[Q]-[#] - cmJumpMark0-9  Move cursor to specified place marker (0 to 9).
  892.       [Ctrl]-[Q]-[A] - cmReplace      Search for text and replace it.
  893.       [Ctrl]-[Q]-[C] - cmTextEnd      Move cursor to end of document.
  894.       [Ctrl]-[Q]-[D] - cmLineEnd      Move cursor to end of line.
  895.       [Ctrl]-[Q]-[F] - cmFind         Find specified text.
  896.       [Ctrl]-[Q]-[H] - cmDelStart     Delete text from cursor to start of line.
  897.       [Ctrl]-[Q]-[I] - cmIndentMode   Toggle IndentMode on and off.
  898.       [Ctrl]-[Q]-[R] - cmTextStart    Move cursor to beginning of document.
  899.       [Ctrl]-[Q]-[S] - cmLineStart    Move cursor to beginning of line
  900.       [Ctrl]-[Q]-[U] - cmReformDoc    Reformat the document text.
  901.       [Ctrl]-[Q]-[Y] - cmDelEnd       Delete text from cursor to end of line.
  902.           [Ctrl]-[R] - cmPageUp       Page up one screen.
  903.           [Ctrl]-[S] - cmCharLeft     Move cursor left one character.
  904.           [Ctrl]-[T] - cmDelWord      Delete text from current cursor to next word.
  905.           [Ctrl]-[U] - cmUndo         Restore text to previous state.
  906.           [Ctrl]-[V] - cmInsMode      Toggle cursor between insert and overstrike mode.
  907.           [Ctrl]-[W] - cmScrollUp     Scroll screen up one line keeping cursor position.
  908.           [Ctrl]-[X] - cmLineDown     Move cursor down one line.
  909.           [Ctrl]-[Y] - cmDelLine      Delete line of text cursor is on.
  910.           [Ctrl]-[Z] - cmScrollDown   Scroll screen down one line keeping cursor position.
  911.          [Backspace] - cmBackspace    Delete character to left of cursor.
  912.         [Left Arrow] - cmCharLeft     Move cursor left one character.
  913.        [Right Arrow] - cmCharRight    Move cursor right one character.
  914.  [Ctrl]-[Left Arrow] - cmWordLeft     Move cursor left to previous word.
  915. [Ctrl]-[Right Arrow] - cmWordRight    Move cursor right to next word.
  916.               [Home] - cmLineStart    Move cursor to start of line.
  917.                [End] - cmLineEnd      Move cursor to end of line.
  918.        [Ctrl]-[Home] - cmHomePage     Move cursor to top of current page.
  919.         [Ctrl]-[End] - cmEndPage      Move cursor to bottom of current page.
  920.           [Up Arrow] - cmLineUp       Move cursor up one line.
  921.         [Down Arrow] - cmLineDown     Move cursor down one line.
  922.               [PgUp] - cmPageUp       Page up one screen.
  923.               [PgDn] - cmPageDown     Page down one screen.
  924.        [Ctrl]-[PgUp] - cmTextStart    Move cursor to beginning of document.
  925.        [Ctrl]-[PgDn] - cmTextEnd      Move cursor to end of document.
  926.                [Ins] - cmInsMode      Toggle cursor between insert and overstrike mode.
  927.                [Del] - cmDelChar      Delete character cursor is on or selected text.
  928.   [Ctrl]-[BackSpace] - cmDelStart     Delete text from cursor to start of line.
  929.        [Shift]-[Ins] - cmPaste        Copy text from clipboard into document.
  930.        [Shift]-[Del] - cmCut          Remove text from document into clipboard.
  931.         [Ctrl]-[Ins] - cmCopy         Copy text from document into clipboard.
  932.         [Ctrl]-[Del] - cmClear        Clear out and dispose of clipboard contents.
  933.  
  934. * NOTE:  # implies a numeric value between 1 and 0 on the
  935.          keyboard.  (i.e., 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
  936.  
  937.  
  938.  
  939. IDE COMMANDS NOT SUPPORTED
  940. --------------------------
  941.  
  942. [Ctrl]-[I]     - Insert a real tab character.
  943. [Ctrl]-[K]-[I] - Indent marked block.
  944. [Ctrl]-[K]-[P] - Print marked block
  945. [Ctrl]-[K]-[R] - Read a file from disk and insert into document as marked block.
  946. [Ctrl]-[K]-[U] - Unindent marked block.
  947. [Ctrl]-[K]-[V] - Move marked block.
  948. [Ctrl]-[K]-[W] - Write marked block to a file on disk.
  949. [Ctrl]-[O]-[T] - Toggle between real tabs and spaced tabs.
  950. [Ctrl]-[P]-[?] - Insert a control character (where ? is the character).
  951.  
  952.  
  953.  
  954. COMMANDS THAT DIFFER FROM IDE
  955. -----------------------------
  956.  
  957. Command              NewEdit                            IDE
  958. -------              -------                            ---
  959.  
  960. [Ctrl]-[K]-[F]     - Save document to a new file.       ----
  961. [Ctrl]-[O]-[I]     - Set tab stops                      Toggle AutoIndent Mode
  962. [Ctrl]-[Q]-[I]     - Toggle AutoIndent Mode             ----
  963. [Ctrl]-[BackSpace] - Delete from cursor to left margin. ----
  964. UNDO Menu Option   - Restore the line.                  [Ctrl]-[Q]-[L]
  965.  
  966.  
  967.  
  968. HOW TO ADD CUSTOM KEY COMMANDS
  969. ------------------------------
  970.  
  971. Adding your own custom key commands, and supporting code, is an
  972. easy matter.  The only requirement is you must understand how
  973. the different key arrays are structured.  As an example, we'll
  974. consider adding user print controls to the editor.
  975.  
  976. We start with the FirstKeys array.  Note the array size in the
  977. following code fragment:
  978.  
  979.   FirstKeys : array[0..46 * 2] of Word = (46, Ord (^A), cmWordLeft,
  980.  
  981. The array has 46 elements.  Each element is a word (two bytes).
  982. That's what the 46 * 2 means.  This is important, for it is how
  983. the editor determines which key stroke, or control sequence has
  984. been pressed.
  985.  
  986. You will note that FirstKeys consists entirely of control key
  987. sequences, ^A, ^B, ^C, etc.  To each control key a command is
  988. tied, cmWordLeft, cmReformPara, cmPageDown, etc.  Not only are
  989. control keys found in this array, but also cursor control keys
  990. like kbHome, kbEnd, kbUp, etc.  So far, it's pretty simple.
  991.  
  992. Now, notice that three control keys have a hex word value
  993. assigned to them, ^Q=$FF01, ^K=$FF02, ^O=$FF03, ^J=$FF04.
  994. Just what are these word values?  They are flags that let the
  995. NEWEDIT unit know when a special control sequence has been
  996. pressed that requires further evaluation of an additional key
  997. to complete the command.  For example, ^QI, ^KB, ^OR, or ^JL.
  998. How does it work?
  999.  
  1000. The answer is the high byte in the command tied to the keystroke,
  1001. which is $FF for these four special keys.  This is a flag that
  1002. tells the ConvertEvent procedure to process the key AFTER the
  1003. control key as the actual command.  How does it process it?  To
  1004. answer that question, we need to dig into the other arrays
  1005. first.
  1006.  
  1007. Notice that after the FirstKeys array there are several other
  1008. arrays:  QuickKeys, BlockKeys, FormatKeys, JumpKeys, and
  1009. finally KeyMap. The QuickKeys, BlockKeys, FormatKeys, and
  1010. JumpKeys are arrays that are tied to the ^Q, ^K, ^O, and ^J
  1011. keys. These arrays define the possible key commands that could
  1012. be tied to these keys.  To illustrate this, note that FormatKey
  1013. defines all the valid keystrokes allowed for the ^O command in
  1014. FirstKeys.
  1015.  
  1016. The factor that ties the FormatKey array to the ^O command in
  1017. the FirstKeys array is the low byte in the $FF01 command tied
  1018. to ^O.  How does this work?  Very simply put, the order you
  1019. place your array into the KeyMap array is the determining
  1020. factor.  Let's look at KeyMap.
  1021.  
  1022.   KeyMap : array[0..4] of Pointer = (@FirstKeys,
  1023.                                      @QuickKeys,
  1024.                                      @BlockKeys,
  1025.                                      @FormatKeys,
  1026.                                      @JumpKeys);
  1027.  
  1028. Note the order of the array elements in Keymap, and the low
  1029. byte of the commands tied to the control keys we've been
  1030. discussing.
  1031.  
  1032.   0  FirstKeys
  1033.   1  QuickKeys  ^Q  $FF01
  1034.   2  BlockKeys  ^K  $FF02
  1035.   3  FormatKeys ^O  $FF03
  1036.   4  JumpKeys   ^J  $FF04
  1037.  
  1038. Do you see the relation?  When ConvertEvent sees the $FF as the
  1039. high byte in the word of the event, it knows that one of these
  1040. special control key values has been pressed.  It then looks for
  1041. the low byte of the word, and jumps into the KeyMap array to
  1042. determine the address of the Pointer to the proper array to get
  1043. the next key interpretation from, based on the position in the
  1044. KeyMap array corresponding to the low byte.  It then goes to
  1045. that array, finds the correct key, and processes it.  For
  1046. example,
  1047.  
  1048.   ^OR
  1049.  
  1050.   1)  ^O pressed, look it up in FirstKeys.
  1051.   2)  command is $FF03.
  1052.   3)  ConvertEvent finds $FF in the high byte of the word.
  1053.   4)  ConvertEvent knows more data follows and finds the low
  1054.       byte of the command word.
  1055.   5)  The low byte is $03 so ConvertEvent accesses the 3rd
  1056.       element of KeyMap to find the address of FormatKeys.
  1057.   6)  "R" is found in FormatKeys and the command tied to it,
  1058.       cmRightMargin, is placed on the event queue for
  1059.       HandleEvent to process.
  1060.  
  1061. So, how do we add new commands tied to the ^P key for printing
  1062. options?
  1063.  
  1064. First, define some new commands up in the constant area where
  1065. the cm??? commands are defined.  Let's say we want ^PD to print
  1066. the entire document and ^PC to print the document starting at
  1067. the current cursor position.  So we add new commands called
  1068. cmPrintStart and cmPrintCurrent and assign them a value not
  1069. being used by other cm??? commands.  So far, so good
  1070.  
  1071. Second we go to the FirstKeys array and insert ^P as a new
  1072. command.  We have to change the array size (46) to show we
  1073. have a new command (change 46 to 47 -- BOTH 46's to 47's).  Now
  1074. tie a new command to ^P, in this case it would be $FF05.  So,
  1075. we would have something like this:
  1076.  
  1077. CONST
  1078.  ...
  1079.  cmPrintFirst   = 550
  1080.  cmPrintCurrent = 551
  1081.  
  1082.   FirstKeys : array[0..46 * 2] of Word = (46, Ord (^A),    cmWordLeft,
  1083.                                               Ord (^B),    cmReformPara,
  1084.                                               Ord (^C),    cmPageDown,
  1085.                                               Ord (^D),    cmCharRight,
  1086.                                               Ord (^E),    cmLineUp,
  1087.                                               Ord (^F),    cmWordRight,
  1088.                                               Ord (^G),    cmDelChar,
  1089.                                               Ord (^H),    cmBackSpace,
  1090.                                               Ord (^I),    cmTabKey,
  1091.                                               Ord (^J),    $FF04,,
  1092.                                               Ord (^K),    $FF02,
  1093.                                               Ord (^L),    cmSearchAgain,
  1094.                                               Ord (^M),    cmNewLine,
  1095.                                               Ord (^N),    cmInsertLine,
  1096.                                               Ord (^O),    $FF03,
  1097.                                               Ord (^P),    $FF05,
  1098.                                               ....
  1099.  
  1100. Now we create a new array to hold the print commands.  We'll
  1101. call it PrintKeys.
  1102.  
  1103.   PrintKeys : array[0..2 * 2] of Word = (2,  Ord ('C'), cmPrintCurrent,
  1104.                                              Ord ('D'), cmPrintStart);
  1105.  
  1106. Last of all we add PrintKeys to the 5th ($FF05) position in
  1107. KeyMap. Don't forget to change the array range from 4 to 5!
  1108.  
  1109.   KeyMap : array[0..5] of Pointer = (@FirstKeys,
  1110.                                      @QuickKeys,
  1111.                                      @BlockKeys,
  1112.                                      @FormatKeys,
  1113.                                      @JumpKeys,
  1114.                                      @PrintKeys);
  1115.  
  1116. Now, all you need do is add the cmPrintStart and cmPrintCurrent
  1117. commands to HandleEvent, with the appropriate procedure or
  1118. function call.  Coding these procedures or functions is up to
  1119. you!  Don't forget to include the declaration for the procedure
  1120. or function inside private portion the TEditor object!
  1121.  
  1122. Heck!  Here's some code Steve Schafer posted on BPROGA that I modified to
  1123. conform to my coding style.  It should get you started.
  1124.  
  1125.  
  1126.         uses ... Printer;
  1127.  
  1128.         procedure TEditor.Print_Document;
  1129.  
  1130.         VAR
  1131.  
  1132.           P: Word;                      { Position of CurPtr.          }
  1133.  
  1134.         begin
  1135.  
  1136.           for P := 0 to BufLen do       { Print from beginning to end. }
  1137.  
  1138.             Write (Lst, BufChar (P));   { Print the character.         }
  1139.  
  1140.         end { TEditor.Print_Document };
  1141.