Code Design for the "ee" Editor ----------------------------------------------- Contents 1. Refactoring 2. The Design Goals A. for maintainability B. for reliability 3. The Design Strategies ( and tactics ? ) A. Namespaces and Object Code Components B. Arithmetic C. "const" pointers D. Positions E. Document-View Architecture F. Callbacks 4. The Design of Individual Components A. The Application Model B. The Document C. The View 5. Source File Descriptions 6. The Code Hierarchy 1. Refactoring One aspect of the "refactoring" was to simplify complex code. One example was the code for dealing with a character being inserted, e.g., when the user typed an "a". The code directly updated all the data that could be affected by that, including cursor positions, book marks, a highlighted block, ... . Doing the adjustments in the right order was so complex that it never seemed clear that the code was correct. This led to the idea of having positions in the text that would automatically adjust when characters were inserted. There was then less complexity in the higher level handler code. The complexity was hidden at lower levels. 2. The Design Goals A. high maintainability We want the code to be easily enhanced, to support the requirement that it be a customizable editor, through the user changing the source code. One aspect of this is readability. A maintainer should be able to start reading the code anywhere, and understand what it is doing locally, even without knowing how it fits in globally. This is somewhat analogous to starting to read a book in the middle. B. commercial reliability The code should have the reliability expected of commercial products. So it is is designed for automated testing. We allow for progammatic simulation of user input ( keystrokes ), and programmatic capture of the output to the user ( characters on the screen ). 3. The Design Strategies and Tactics A. Namespaces and Object Code Components The project seemed not quite big enough to use namespaces or object code components ( dll's or libraries ). If it had been slightly larger, we would have. As a terminology note, later we will use "component" when we talk about the document-view architecture. B. Arithmetic Much of the arithmetic is done with 32-bit integers. There is a danger with any finite precision integer of wrapping around, e. g., going past the maximum value, and then having a small value, that is not intended. The other way to wrap around is to go below the minimum value. Wrapping can happen with signed or unsigned values. If the source code were in C#, we could make sections of the code "checked", and it would throw an exception on wrap-araound. Our strategy is to limit the size of the text storage area to one half the maximum value of an unsigned 32-bit number. That way any difference between two positions can be stored in a signed 32-bit number. Also, any arithmetic to move from one position to another in the text storage area can be done with 32-bit numbers, without danger of wrapping. C. "const" pointers We have some handles ( pointers ), to windows and files, that from the outside are just handles. They are similar to handles to windows, ... in Windows programming. Inside the lower level code that manipulates the windows, they are pointers to the windows. From the outside they can be treated as "const" pointers. In the lower level code, they are often cast to non-const. One example is in the list-of-files. D. Positions The cursor position, a bookmark, and corners of a highlighted block are all positions that have two senses of "position". They have positions on the screen, and positions in the text that is being edited. For example, the cursor could be on the second column of the fourth row of the screen, and it could be on the 82nd column and 75th line in the file. These two senses of position have a fairly simple relationship, based on how much the window has moved ( scrolled ) over the text. The window will have a vertical position for how many lines it has scrolled down, and a horizontal position for how much it has scrolled to the right. Then any 2D position in a file is equal to the position in the window plus ( vector addition ) the amount the window has scrolled. Each window will keep track of its two scrolling amounts. So we won't need to store both senses of position for the cursor, any bookmark, ... . If we store a position in a file, we can always easily calculate a position on the screen. Thinking about, e.g., a book mark, we would like a position in a file to be "smart". If text is inserted or deleted before that position, the pointer pointer needs to shift. If that can happen automatically, it will simplify the higher levels of code. The "text storage area" will be the object that has methods for inserting and deleting text. So we will make "positions" that the text storage area knows about, so they can be adjusted automatically. They will be objects. Some of the positions will be associated with a line number. For example, we want to track the line number of the cursor, because we display the line number in the window status line. We'd like the line numbers to be smart also, by changing automatically when text is inserted or deleted before their position. We will have objects that are smart line numbers, and objects that are combined line numbers and positions. E. Document-View Architecture There is general agreement on separating the code for viewing information from the code for changing the information, in the top-level architecture. Beyond that there is some divergence of opinion, about how to group the code. Usually each document can have several views. Some applications can have only one view, or only one document. The specifications for this editor allow for several documents, and several views for each document. Some advocate a Document-View-Controller architecture, in which the Controller handles the input from the user, and the View handles the output to the user. When a change from the user needs to go to the Document and its Views, e.g., when the user types an "a", there are several possibilities for communication : 1. The Controller can send a message to the Document, and then to the View. 2. The Controller can send a message to the Document, which sends messages to each of its Views. 3. The Controller can send a message to a View, which sends a message to its Document. Then somebody still needs to send a message to the other views for the Document. Some call the three-part design the Model-View-Controller architecture. Some separate the Model into two parts, the "Document-Model" and the "Application-Model". In this paradigm, the Controller is a fairly simple dispatcher, and the "Application-Model" handles any significant application logic, that is not in the Document or View. For example, if the user asks to quit, but has not saved his changes, the decision to ask whether to save would be in the "Application-Model". This four-part design is how this editor's code is organized. We use the communication type 2 from above, except it's the "Application-Model" that sends the messages, instead of the Controller. The messages from the document to its views are sent by "callbacks". In this design, the document doesn't know anything about its views, except that it knows to call some registered "callbacks". Outside of the callbacks, the views only read information from the document, i.e., they call only "const" methods. There are also some "utilities" that can be used in any other part, e.g., a list container, so the "utilities" are a fifth component. Here is a list of our componenets: A. An editor, which serves as a container for the other components B. A controller, which dispatches commands to the appropriate handlers C. A document, that holds the information being edited. D. A view, that displays the document to the user E. An application model, that has the logic for how the application operates, other than the logic in the document, view, ... . F. "utilities", that are general-purpose, e.g., containers, or code dependent on the execution or development platforms. Here is an overview of what happens when the user types an "A" . First we send a message to the document, to store the "A". Assuming the editor is in insert mode, that automatically advances the cursor positions ( in the text, not on the screen ) for any affected windows. Then the document sends a message to all the windows for the file that was changed, to shift right if necessary, to keep the cursor visible in the window. If a window is the active window, it displays the cursor at the ( now determined ) position. F. Callbacks To be able to draw itself, the view needs some information about a highlighted block. As mentioned above, the document keeps a list of other components to notify when the document has changed. 4. The Design of Individual Components Source files typically have some design comments. If a .cpp file has a corresponding .h file, the comments will be in the .cpp file. A. The Application Model This will have a highlighted block, which is a block that can be copied, moved, deleted, ... . It will also contain the help text, an ASCII chart, and text for the credits. B. The Document This includes a text storage area, and a list of files. C. The View This has a list of windows. The view also has a global status bar, that shows whether the editor is in insert mode, ... . The knowledge of which window is active ( which implies which file is active ) is kept in the list of windows. That creates a temptation to move code that depends on which window is active, from the application model into the list of windows. But that could result in the view giving commands to the document, so we avoid it. 5. Source File Descriptions Typically a class name corresponds with a file name, with blanks from the file name replaced by underscores, and with "_class" appended. E.g., the files "list of windows" ( .cpp and .h ) contain the class "list_of_windows_class" . application model.cpp, .h Logic that does not go in the Document, View, ... . array of strings.h an array of strings ascii table.cpp, .h An ASCII table to show the user. command.h Just a list of the commands -- save, exit, ... . compiler dependent.h Code that needs to change if we change compilers controller.cpp, .h Dispatches a command from the user to the, appropriate handler code. credits.cpp, .h Tells who wrote the code. dialog.cpp, .h Asks the user a question, and gets the answer editor.cpp Initializes global data, and runs a command loop. Also, the entry point for code execution. file opener.cpp, .h takes a list of file specs, like "main.cpp editor.h", and opens the files global status bar.cpp, .h The global status bar help text.cpp, .h Some help for the user highlighted block.cpp, .h A block the user may want to copy, delete, move, ... . list of files.cpp, .h All the files that are open for editing list of windows.cpp, .h All the windows that are open platform command line parser.cpp, .h Code for command line parsing, that may change with the execution platform. platform file attributes.cpp, .h Code for file attributes ( read only, ... ), that may change with the execution platform. platform file finder.cpp, .h Code for finding files that match a spec, e.g., *.cpp, and that may change with the execution platform. platform input.cpp, .h Code for getting input ( keystrokes ) from the user, and that may change with the execution platform. platform output.cpp, .h Code for sending output ( characters ) to the user, and that may change with the execution platform. simple array.h a generic array, in the spirit of STL, but simpler simple list.h a generic list, in the spirit of STL, but simpler strings.cpp, h common functions for strings of characters text searcher.cpp, .h Code to search for a string of characters text storage area.cpp, .h Where all the text that is being edited is stored. 6. The Code Hierarchy It's probably more helpful to think of the hierarchy of the components, rather than a hierarchy of individual source files. In that case, we would have, from top down, A. the editor B. the controller C. the application model D. the view E. the document F. "utilities" . But if we look at individual source files, then we get the list below. file / class level depends on editor 9 controller, application model, dialog, global status bar, file opener, list of windows, list of files, text storage_area, text searcher, platform input, platform output, strings controller 8 application model, list of windows, global status bar, list of files, platform input, platform output application model 7 file opener, dialog, highlighted block, help text, ascii table, credits, global status bar, list of files, text storage area, platform input, platform output, text searcher, command, strings file opener 6 dialog, list of windows, global status bar, list of files, text storage area, platform file finder, platform command line parser, platform input, platform output, strings dialog 5 global status bar, list of windows, platform input, platform output, array of strings, command, strings highlighted block 5 list of windows, list of files, text storage area list of windows 4 list of files, text storage area, simple list, platform output, strings global status bar 4 list of files, platform output list of files 3 text storage area, platform file finder, platform file attributes, strings, simple list ascii table 3 array_of_strings help text 3 array_of_strings credits 3 array_of_strings text storage area 2 text searcher, simple array, strings, compiler dependent platform file finder 2 strings platform input 2 command platform output 2 compiler dependent array of strings 2 compiler dependent platform command line parser 1 ( none ) platform file attributes 1 ( none ) simple array 1 ( none ) simple list 1 ( none ) command 1 ( none ) strings 1 ( none ) compiler dependent 1 ( none ) text searcher 1 ( none )