oster@dewey.soe.berkeley.edu (David Phillip Oster) (09/18/87)
Now that the system software has been extended to include a version of TextEdit that supports multiple fonts, sizes, styles (and colors) all within a single text edit record, it is possible for programs based on TextEdit to allow users to use multiple fonts. With new text edit, each word (and potentially each character) can have its own font. Almost any program that doesn't allow multiple fonts could be extended to allow them. Examples include: o Calendar (Mosaic Codes) o MockWrite (C.E. Software) o MiniWriter (Maitreya Design) o Acta (Maitreya Design) and, of course, hundreds more. This document lists the problems I had to solve to really do a textEdit text editor right. I am giving this hard won information away. This information cost me many sleepness nights to develop and implement. If all developers pay attention, then I, as a user, will be able to buy a better set of products. So all you developers: listen up! This is part one. It is a list of the problems you need to solve. Part two is a list of proposed standards, that if everyone followed, would make everyone's life easier. I had to solve the following problems to actually use new text edit in a program: (a) Tech Note #131, listing bugs in new TextEdit (b) Scrolling code changes (c) Font/size/style/color menus & dialog (d) Insertion point font/size/style/color (e) cut/copy/paste undo (f) search and replace (g) alternate forms for deficient systems (h) smart quotes (i) Bottom of page concerns (j) arrows and extended keyboard. (k) smart cut and paste (l) preserving print record and window size (m) save and multi-tasking issues. (n) standard close/quit box. (a) Tech Note #131 gives a list of bugs and work arounds for new TextEdit. I had to do all that. (b) My scrolling code had to be changed to handle the fact that, if lines have different width, the number of lines that fit on the screen is a function of position. The three important positions are: (b.1) the number of lines to page forward if the user clicks in the pageDown area of the scroll bar. (b.2) the number of lines to page back if the user clicks in the pageUp area. (b.3) the number of lines that fit at the bottom of the screen, so we set the CtlMax of the scroll bar correctly. (CtlMax = totalNumberofLines - 1 - LinesThatFitAtBottom) (c) you need to provide a way for the user to change the new attributes of a selected region. I provide font/size/style/color menus. I also provide: (c.1) a dialog for setting all these attributes at once. Rather than use pop-up menus, I think it is better to use SFGetFile style lists with a scroll bar. They are easier for the user to cope with. This dialog also includes an EditText field for the user to type in numbers. (c.2) a dialog for adding named colors to the color menu (use the color picker, then type in a color name. The color name gets sorted into the menu of available colors.) This dialog also contains a scrollable list of colors, so that the user can select color to remove from the menu. (c.3) I correctly remembered that your font/size/style/color menus and dialog should show checkmarks to let the user know the current state. Remember that your size menu should use outline font to let the user know which sizes in the currently selected font are actually available a bitmaps and which will be synthesized. (Note that there is no good way to convey this information for styles, like underline, which may now also have their own bitmaps.) (c.4) I wrote a custom menu definition procedure that shows each font in that font (chicago is in chicago, geneva in geneva, and new your in new york.) My menu definition procedure handles scroll arrows at the top and bottom, when required, and since not all fonts are the same height, handles scrolling of variable sized objects. I use this code in my font&size&style&color dialog to present a scrollable list of fonts that look right. (c.5) Remember that while the font/size/color menus set their values directly into the selected text, the style menu toggles: If there is no checkmark for "underline", and the user selects "underline" then the program should underline the selected text. If there is a check, then, when the user selects underline, it must remove all underlining from the selected area. Note that there is an assymetry here. If ALL the text is underlined, it will be checked, and if ALL the text it not underlined it will be not checked, but if SOME of the text is underlined, it also will not be checked. The program must do the right thing. (d) new text edit does not directly support setting the font/size/style/color state of the inserttion point, when it is just a blinking line. I had to implemnt my own mechanism for setting this state. (e) cut/copy/paste and most important, undo is implemented for every EditText item in every dialog. (including SFPutFile.) Remember, undo is pretty useless without changing the text of the undo item of the edit menu to let people know what operation will be undone: for example, the user does a font change. The undo menu changes to "Undo Font Change ^Z". The user selects it, the menu changes to "Redo Font Change ^Z". Undo is not implelemented in new text edit. I had to do it. (f) search and replace: Now that the user can have multiple fonts and styles, fonts and styles become important for searching. I let the user paste into search and replace EditText items, and also let them use the font/size/style color menus and the font&size&style&color dialog while they are using the search&replace dialog. Next to the search string are 5 checkboxes: Use this when I search _ |X| Text = | | Font = | | Size = | | Style = | | Color - If the types some 12point plain chicago text in the search EditText item, and only has the "text" button checked, then searching behaves like in an old application, i.e. font information is ignored. If the user checks the size box, then only 12point text will match the search string. Next to the replacement EditText item, I have checkboxes that read: Use this when I replace _ |X| Text = | | Font = | | Size = | | Style = | | Color - The meaning here is similar. This extension to the search and replace dialog lets the user do everything he can now, and in addition, do things like: o Find the next underlined occurence of the word "Loisville" o Find all occurences of italic, and turn them to underline o Change all occurences of chicago to geneva bold. All these checkboxes take a lot of room, so I use a custom 9point Chicago font, and a custom control defintion procedure, and I paste the font into my application. Since there is no safe way to add just one point size of a font under the current font manager, this font is called ".Chicago" and therefore doesn't show up on the font menu. Of course, all replacements are undoable (including replace all.) I also provide a menu item: "Go To Previous" that lets the user undo a cursor motion (a "find" is a cursor motion.) (g) alternate forms for deficient systems: If you are running under old text edit, I have alternate forms of all the dialogs and menus that hide the fact that font editing is avialable. If you are running without color quickdraw, I have alternate forms of all the dialogs and menus that hide the fact that color editing is available. What I should have done, and will do, is change the dialogs so that unavailable choices are grayed out, and there is a "help" button in every dialog that explains how to use that dialog and, if choices are grayed out, why they are grayed out. (h) smart quotes, and an option to turn them off. The slanted, typesetter style '` and '' `` quotes look better than the vertical ' and ", but the text editor should allow people to type normally, and automatically replace them (except when the user turns it off, for example, usenet doesn't support them, so I can't use them typing this document.) (i) Bottom of page concerns. When you display text on the screen, it is acceptable, and even desirable that, if the last line doesn't entirely fit, you just show the top part that does fit. Showing this partial line reminds the user that more text is available off the bottom. The exceptions are: (i.1) if the user moves into this line, you need to scroll it so that it is completely visible. (i.2) When printing, you must not show on the printed page lines that extend partially off the bottom of the screen. (j) The Apple user interface guidlines dictate what should happen if the user presses an arrow key, or some of the keys on the extended keyboard: <Home>, <End>, <PageUp>, <PageDown>, and what should happen if the user is also holding down the <Shift>, <Option>, or <Command> keys at the same time. Text Edit does not do most of this stuff, and much that it does do, it does wrong, so you have to do it yourself. I even had to uncover what key codes these keys sent! (j.1) To properly implement auto-scrolling in the face of these keys, you have to keep track of whether the left or the right end of the selection area changed most recently, and auto-scroll that part into visibility (selection areas may be bigger than the currently visible window.) (k) The Apple user interface guidlines dictate that cut and paste should be "smart" and record whether the cut was at a beginning of a word on the left, and the end of a word on the right, so that when you cut it should automatically delete extra white space if necessary, and when you paste, it should automatically insert white space if necesary. Some people hate this feature and need a way to turn it off. (l) preserving print record and window size. It is friendly, and the multi-finder compatibility guidlines recommend, that when the user saves a file, also saved with it are: (l.1) the window position, so that when the user opens that document again, it will show up in the same position of the screen. (l.1.a) remember, when you open a document using a saved window position, that this time the user might be on a system with a smaller screen. Force that window on-screen! Force that window to be smaller than a screen (so the user can use the grow box.) (l.2) as long as we are saving the window position, it would be friendly to also save the cursor position, and what line the document is scrolled to. I think it is obnoxious for a document to open with the cursor off-screen, so I always auto-scroll the document so that the most recently active end of the cursor is visible. (l.3)the print record (if the user printed we should try to give him the same page setup and print quality settings next time.) (m) Suppose the user opens a document in your program, then deletes the data file in multi-finder, or renames it, or moves it to another folder. The Apple user interface guidline people want me to open the file, and not close it again until the user closes or quits. I prefer to handle this problem by reading into memory the entire document, including the finder info and the creation and modification times, and any resources, then closing the file. Let the user do horrible things to the copy on disk. When the user saves, if my code can't find the original file, or if the modification date doesn't match, it puts up an sfPutFile dialog with an explanatory dialog below it on the screen: "Something changed this file since the last time it was saved. I suggest you "Save As" this file with a different name." The advantage of this scheme is there is no limit to the number of open documents you can work with (as opposed to the small number of simultaneously open files) and the program doesn't have to worry about the user renaming an open file, or changing its folder. (Thanks to David Dunham of Maitreya Designs for telling me this idea.) (n) Standard closing/quitting box. A while back, Apple published a user interface guidline note saying that programs should use a standard Closing/Quitting dialog. With the possible use of multi-finder, it is difficult for the user to tell which application owns the dialog. I put my application's icon in the dialog to solve this problem. In part two, I'll talk about disk formats of data structures that I would like declared standard. --- David Phillip Oster --My Good News: "I'm a perfectionist." Arpa: oster@dewey.soe.berkeley.edu --My Bad News: "I don't charge by the hour." Uucp: {uwvax,decvax,ihnp4}!ucbvax!oster%dewey.soe.berkeley.edu
korn@cory.Berkeley.EDU.UUCP (09/19/87)
Somewhere toward the end of a very good and informative posting, David Oster (oster@dewey.soe.berkeley.edu) wrote: > (m) Suppose the user opens a document in your program, then deletes the data > file in multi-finder, or renames it, or moves it to another folder. The Apple > user interface guidline people want me to open the file, and not close it > again until the user closes or quits. > I prefer to handle this problem by reading into memory the entire document, > including the finder info and the creation and modification times, and any > resources, then closing the file. Let the user do horrible things to the > copy on disk. When the user saves, if my code can't find the original file, > or if the modification date doesn't match, it puts up an sfPutFile dialog > with an explanatory dialog below it on the screen: > "Something changed this file since the last time it was saved. I suggest > you "Save As" this file with a different name." > > The advantage of this scheme is there is no limit to the number of open > documents you can work with (as opposed to the small number of simultaneously > open files) and the program doesn't have to worry about the user renaming > an open file, or changing its folder. > > (Thanks to David Dunham of Maitreya Designs for telling me this idea.) The idea is a neat one, but it turns out there is a situation in which it's very *dangerous* Imagine if you will a network. It has many machines on it, most of which are workstations, and some of which are servers. Imagine that two workstations are editing the same file on a server. One read in the file at 1:00pm, the other at 1:18pm. The first one finishes it's changes, and writes it back at 1:20pm. The other finishes at 1:31pm, and writes it's changes _on_top_of_the_first_set_. You immediately tell me "Peter, this can't happen, David's algorythm takes care of that." Well, in theory it does. But in practice, it can fail, because the two Macs in question (the workstations) may have a slightly different idea of what time it is... The mac that gets the file at 1:18pm may be 3 minutes fast, and think that it's really 1:21pm. When that mac goes to write the file back, it notices that the the last save to the file happened 'before' this mac got it, so it 'knows' that it's ok to save on top of it. and whomp-o, you've lost data. (and don't think this doesn't happen in networks; it happens all to often!) A slightly different scheme that works on the same principle might increment some number, an 'in use' number. Each time an application edited a copy of that file, it would increment the 'in use' number. Each time it closed the file, it would decrement that number. If it saw that the number was > 0 at save time, it would give the user the dialog, and let the user decide. This method also has it's problems. It's more complex, and it requires more writes to the file (even if I 'save as', I still have to decrement the 'in use' number). And the file might be replaced with one that has a lower (or higher) 'in use' number than it should have, wreaking havoc. Perhaps the safest way of dealing with it all is to just set the busy bit. That way nobody can mess with the file behind your back (without having to go through an override that requires they think about what they are doing). Peter -- Peter "Arrgh" Korn korn@ucbvax.Berkeley.EDU {decvax,dual,hplabs,sdcsvax,ulysses}!ucbvax!korn
oster@dewey.soe.berkeley.edu (David Phillip Oster) (09/20/87)
In article <20860@ucbvax.BERKELEY.EDU> korn@cory.Berkeley.EDU.UUCP (Peter "Arrgh" Korn) writes: >> (m) Suppose the user opens a document in your program, then deletes the data >> file in multi-finder, or renames it, or moves it to another folder. Apple >> user interface guidline people want me to open the file, and not close it >> again until the user closes or quits. >> I prefer to handle this problem by reading into memory the entire document, >> including the finder info and the creation and modification times, and any >> resources, then closing the file. Let the user do horrible things to the >> copy on disk. When the user saves, if my code can't find the original file, >> or if the modification date doesn't match, it puts up an sfPutFile dialog >> with an explanatory dialog below it on the screen: >> "Something changed this file since the last time it was saved. I suggest >> you "Save As" this file with a different name." >> The advantage of this scheme is there is no limit to the number of open >> documents you can work with (as opposed to the small number simultaneously >> open files) and the program doesn't have to worry about the user renaming >> an open file, or changing its folder. >The idea is a neat one, but it turns out there is a situation in which it's >very *dangerous* Imagine if you will a network. It has many machines on >it, most of which are workstations, and some of which are servers. Imagine >that two workstations are editing the same file on a server. One read in >the file at 1:00pm, the other at 1:18pm. The first one finishes it's changes, >and writes it back at 1:20pm. The other finishes at 1:31pm, and writes it's >changes _on_top_of_the_first_set_. You immediately tell me "Peter, this >can't happen, David's algorythm takes care of that." Well, in theory it >does. But in practice, it can fail, because the two Macs in question >(the workstations) may have a slightly different idea of what time it is... >The mac that gets the file at 1:18pm may be 3 minutes fast, and think that >it's really 1:21pm. When that mac goes to write the file back, it notices >that the the last save to the file happened 'before' this mac got it, so >it 'knows' that it's ok to save on top of it. No, it knows that the file has changed since it read it (it got newer). Nice try, but it still can't happen unless the new save time matches _exactly_. You check for an _exact_ match on the modification time, not just a less than or a greater than. But Peter is right, there is still the chance of a problem. >A slightly different scheme that works on the same principle might increment >some number, an 'in use' number. Each time an application edited a copy >of that file, it would increment the 'in use' number. Each time it closed >the file, it would decrement that number. If it saw that the number was > 0 >at save time, it would give the user the dialog, and let the user decide. >This method also has it's problems. It's more complex, and it requires >more writes to the file (even if I 'save as', I still have to decrement >the 'in use' number). And the file might be replaced with one that has >a lower (or higher) 'in use' number than it should have, wreaking havoc. Not a good scheme, because it requires that every application write into every file it accesses, and most accesses are read accesses. This scheme is almost right, though. Peter, is it really true that if I write a file on a remote machine the write happens with _my_ clock, and not the remote machine's clock? After all, I'm just sending a message to the remote machine to do the write. It is the one that is actually issuing the read and write system calls that change the data on its disk. Why should it use my clock? Since you have access to a net, could you please do the experiment and report back. >Perhaps the safest way of dealing with it all is to just set the busy >bit. That way nobody can mess with the file behind your back (without >having to go through an override that requires they think about what they >are doing). Setting the busy bit, (I think) also counts as a write, so it fails for the same reason given above. I believe that there is no reason to restrict multiple reads, it is just multiple writes that are the problem. Obviously, my model is text editors rather than databases. If I were thinking about databases, I'd need a more sophisticated concurency control system, but a text editor rewrites the entire file on each save. A database program writes pieces of its files, (while those files may still be being actively read by other programs.) Let's combine all of the above into a scheme that should work. Suppose we have a "saveCount" number in the file, that counts the number of times the file has been saved. Using the file goes as follows: 1.) The user selects "open". The software reads the entire file, both forks, and the finder information into memory and closes the file. 2.) Some time later, the user selects "save". The software first 2.a) opens the file read/write. If the open fails (because the file is no longer there, or because it is already in use by another writer) the software puts up its Save As dialog, with the explanatory message "This file has been changed by another program since the last time you saved it." 2.b) If the open succeeds, it reads up the save count to see if that matches what the application thinks it should be. if that doesn't match, it goes into the above SaveAs mode. 2.c) It writes and closes the file. When it wrote the file, it used an updated saveCount. This scheme should be robust, since it doesn't depend on any clock being accurate across the net. It does depend on the fact that AppleShare allows only one process to have a file open for read/write at a time. Therefore, if I get the "write" file descriptor, I have a guarantee that noone else has it. The original scheme, given in "How to write a TEXT editor, (part 1)" is still my favorite, and this modification should only be used if it is necessary. Peter, I hope you will report back soon whether it is necessary or not. --- David Phillip Oster --My Good News: "I'm a perfectionist." Arpa: oster@dewey.soe.berkeley.edu --My Bad News: "I don't charge by the hour." Uucp: {uwvax,decvax,ihnp4}!ucbvax!oster%dewey.soe.berkeley.edu
edusoft@ecf.UUCP (09/21/87)
You bring up legitimate concerns about networked computers. We have a network here, and we have solved the problem about times by resetting the time on each node from the central node. Of course, this is a bit easier in a multi-tasking OS.
cck@cunixc.columbia.edu (Charlie C. Kim) (09/21/87)
On time interlocks: The AppleShare client (at least 1.1) is forced to use a "relative". What happens is that when it connects to the server it gets the time of day in a special format (based relative to Jan 1, 2001). References to time should be based upon this difference between this and the current Macintosh time on the client. Thus, unless the clock stops on a Macintosh, all macs on a network should have the same "relative" time in reference to a particular server. Also, time written, modified, etc. are usually written by the server. This is not to say the client cannot or willnot modify these times, but the above should alleviate the problems. I believe the preferable method to interlocking files is to use the "byte range lock" facility outlined in Inside Macintosh Volume 4. I would not depend on the AppleShare deny read/deny write, etc. open permissions. If you want to lock entire file, don't do what MacWrite does though: it locks 0x7fffffff bytes - there is a method for locking the entire file (I can't remember it happens if you specify 0 bytes or 0xffffffff bytes). Charlie C. Kim User Services Columbia University