Turbo Vision Palettes ===================== Objects in Turbo Vision can be grouped into two broad classes: those which are descendants of TView (such as TWindow or TButton), and those which are not (such as TCollection). The difference, of course, is that objects which are descended from TView (also called "views" or "view objects") are intended to be displayed on the computer's screen at some point in their lifetimes. Every descendant of TView contains a Draw method, which is executed whenever the view needs to redraw itself. Although every view's Draw method is different, they all share one characteristic: they call GetColor to determine what on-screen colors to use when drawing the various parts of the view. GetColor in turn calls GetPalette, which returns a pointer to the view's palette. What is a palette? In Turbo Vision, a palette acts as a translation table. In much the same way that the ASCII code maps byte values onto characters, a view's palette maps the set of colors used by the view onto the palette of the view's owner. Let's look at the palette of the TLabel view as an example. (It's shown at the end of the description of TLabel in the Turbo Vision Guide.) We see that the palette has four entries, numbered 1 through 4. TLabel's Draw method knows that when it wants to draw normal text, it should use color number 1. But it doesn't know what color number 1 really is, and it doesn't care. It simply calls GetColor (1) and uses the color that GetColor returns. GetColor calls GetPalette, which returns a pointer to TLabel's palette. From the values contained in the palette, GetColor determines that for TLabel, color number 1 is equivalent to TLabel's owner's color number 7. GetColor then calls Owner^.GetColor (7); the owner view then goes through the same procedure, using its own palette to perform another level of translation, and so on, until the ultimate owner is reached, which in most Turbo Vision programs is a TApplication object (or descendant). The TApplication object is the final color arbiter, and provides all of the views with the actual video attributes to use. Let's trace through the TLabel color hierarchy: We've already determined that TLabel color number 1 maps onto its owner's color number 7. The description of TLabel's palette in the TV Guide states that TLabel's palette maps onto the standard dialog palette. This tells us that TLabel objects are intended to be owned by (i.e., inserted into) TDialog objects. If we now turn to TDialog's palette description, we see that color number 7 (called "Label Normal") is mapped onto TDialog's owner's color number 38. Well, dialog boxes are usually inserted into the desktop, so let's look at TDeskTop's palette. When we do that, we see that TDeskTop doesn't have a palette; this means that TDeskTop doesn't do any color translation--TDialog's color number 38 "falls through" to TDeskTop's owner's palette. Well, the desktop is owned by the application, so we've reached the end of the chain. When TLabel's Draw method wants to use color number 1, it eventually gets told to use the application's color number 38. TApplication's palettes are not shown in the TV Guide, but we can find them in the file APP.PAS in the TVISION directory. TApplication actually uses one of three different palettes, depending on whether the program is being run on a color, black and white, or monochrome monitor. For the purposes of this example, we'll assume that we're using a color monitor. If we locate the 38th entry in TApplication's palette, we find that it is equal to $70. The upper nybble of the byte gives the background color, and the lower nybble the foreground color, according to the following table: 0 - black 4 - red 8 - dark gray C - light red 1 - blue 5 - magenta 9 - light blue D - light magenta 2 - green 6 - brown A - light green E - yellow 3 - cyan 7 - light gray B - light cyan F - white So, we see that a normal label has black text on a light gray background. All of the other colors may be tracked down in a similar manner. What happens if rather than inserting a TLabel into a TDialog, we insert it into a TWindow? Well, let's follow the mapping, again using the "Normal Text" color: TLabel color number 1 -> TWindow color number 7 -> TApplication color number 14, 22, or 30, depending on whether the window is a blue, cyan, or gray window, respectively. These entries correspond to blue text on gray, blue text on green, or white text on light gray. Obviously, none of these are the same as the black text on light gray of a TLabel inserted into a TDialog. This points out a universal truth of Turbo Vision palettes: If a view is designed to be inserted into a particular type of owner view, inserting it into a different type of owner will almost always result in a change in color. Anyone who has played around much with Turbo Vision has encountered the situation where a view is displayed in flashing white text on a red background. This happens when a call to GetColor is made with a color number that exceeds the size of the view's palette. For example, let's see what happens when we insert a TListBox into a TWindow, rather than a TDialog. (Note: The TV Guide says that TListBox's palette maps onto the application palette. This is incorrect; it actually maps onto TDialog's palette.) TListBox has a five-entry palette which maps onto entries 26 through 29 in its owner's palette. Well, lo and behold, a TWindow has only eight entries in its palette--obtaining the 26th entry is impossible. In this situation, GetColor returns the flashing white on red color to signal the error. Here is a list of all of the entries in TApplication's palette, along with the objects that use them: 1 Background (DeskTop) 2 Text Normal (Menu) 3 Text Disabled (Menu) 4 Text Shortcut (Menu) 5 Selected Normal (Menu) 6 Selected Disabled (Menu) 7 Selected Shortcut (Menu) 8 Frame Passive (Blue Window) 9 Frame Active (Blue Window) 10 Frame Icon (Blue Window) 11 ScrollBar Page (Blue Window) 12 ScrollBar Reserved (Blue Window) 13 Scroller Normal Text (Blue Window) 14 Scroller Selected Text (Blue Window) 15 Reserved (Blue Window) 16 Frame Passive (Cyan Window) 17 Frame Active (Cyan Window) 18 Frame Icon (Cyan Window) 19 ScrollBar Page (Cyan Window) 20 ScrollBar Reserved (Cyan Window) 21 Scroller Normal Text (Cyan Window) 22 Scroller Selected Text (Cyan Window) 23 Reserved (Cyan Window) 24 Frame Passive (Gray Window) 25 Frame Active (Gray Window) 26 Frame Icon (Gray Window) 27 ScrollBar Page (Gray Window) 28 ScrollBar Reserved (Gray Window) 29 Scroller Normal Text (Gray Window) 30 Scroller Selected Text (Gray Window) 31 Reserved (Gray Window) 32 Frame Passive (Dialog) 33 Frame Active (Dialog) 34 Frame Icon (Dialog) 35 ScrollBar Page (Dialog) 36 ScrollBar Controls (Dialog) 37 StaticText (Dialog) 38 Label Normal (Dialog) 39 Label Highlight(Dialog) 40 Label Shortcut (Dialog) 41 Button Normal (Dialog) 42 Button Default (Dialog) 43 Button Selected (Dialog) 44 Button Disabled (Dialog) 45 Button Shortcut (Dialog) 46 Button Shadow (Dialog) 47 Cluster Normal (Dialog) 48 Cluster Selected (Dialog) 49 Cluster Shortcut (Dialog) 50 InputLine Normal (Dialog) 51 InputLine Selected (Dialog) 52 InputLine Arrows (Dialog) 53 History Arrow (Dialog) 54 History Sides (Dialog) 55 HistoryWindow ScrollBar page (Dialog) 56 HistoryWindow ScrollBar controls (Dialog) 57 ListViewer Normal (Dialog) 58 ListViewer Focused (Dialog) 59 ListViewer Selected (Dialog) 60 ListViewer Divider (Dialog) 61 InfoPane (Dialog) 62 Reserved (Dialog) 63 Reserved (Dialog) What about changing colors in Turbo Vision? If all you want to do is change the color of all instances of an object, say, by making all of your TButtons cyan instead of green, you've got it easy. You just change the appropriate entries in TApplication's palette (41 through 46), and you're set. That was easy. Now, what about creating a new, unique view which is unlike any predefined Turbo Vision objects? How will we color it? Let's say we want to insert our new view (call it a TNewView) into a TDialog, and we want to use two different colors, one for normal text and one for highlighted text. First, we add two entries to TApplication's palette (numbers 64 and 65) that will correspond to the two colors used by our new view. For the purposes of this example, we'll say we want blue on light gray ($71) for normal text and light green on light gray ($7A) for highlighted text (assuming a color monitor). Our TApplication palette will now look like this: CColor = #$71#$70#$78#$74#$20#$28#$24#$17#$1F#$1A#$31#$31#$1E#$71#$00 + #$37#$3F#$3A#$13#$13#$3E#$21#$00#$70#$7F#$7A#$13#$13#$70#$7F + #$00#$70#$7F#$7A#$13#$13#$70#$70#$7F#$7E#$20#$2B#$2F#$78#$2E + #$70#$30#$3F#$3E#$1F#$2F#$1A#$20#$72#$31#$31#$30#$2F#$3E#$31 + #$13#$00#$00#$71#$7A; { <- the last two are the new entries } We must make similar changes in the black & white and monochrome palettes, of course. Next, since we will be inserting TNewView into a TDialog, we need to override TDialog's GetPalette method so that it will supply GetColor with the proper palette: const CNewDialog = CDialog + #64#65; type TNewDialog = object (TDialog) function GetPalette: PPalette; virtual; end; . . . function TNewDialog.GetPalette: PPalette; const P: String[Length (CNewDialog)] = CNewDialog; begin GetPalette := @P; end; Since we added our two new colors to the end of the standard TDialog palette, which contains 32 entries, they will be the 33rd and 34th entries in TNewDialog's palette. Now we have to define our TNewView so that it maps onto the 33rd and 34th entry of its owner's palette: const CNewView = #33#34; type TNewView = object (TView) function GetPalette: PPalette; virtual; . . . end; function TNewView.GetPalette: PPalette; const P: String[Length (CNewView)] = CNewView; begin GetPalette := @P; end; There. That wasn't so bad, was it? When TNewView's Draw method asks for color number 1, it will get color number 64 from TApplication's palette; similarly, color number 2 leads to TApplication's color number 65. If we ever want to change the colors of our TNewView object, we simply change the entries in TApplication's palette. Okay, let's try something a bit trickier. Let's say we want to insert a view into an owner which is not of the "correct" type. We already know that unless we modify the palettes and associated methods, the colors will come out wrong. The most general solution to the problem is to define a new object type, as in the previous example. Thus, if we wanted to insert a TButton into a TWindow, we would define a descendant of TButton (called TWindowButton, perhaps) and follow the same steps we performed above to give it a set of colors to use. In some cases, we don't need to add to TApplication's palette. In the previous example, if all we want to do is put a button in a window, and we want the button to look just like an ordinary TButton inserted into a TDialog, we can use the same TApplication palette entries (41 through 46): const CNewWindow = CGrayWindow + #41#42#43#44#45#46; CWindowButton = #9#10#11#12#13#13#13#14; type TNewWindow = object (TWindow) function GetPalette: PPalette; virtual; end; TWindowButton = object (TButton) function GetPalette: PPalette; virtual; end; The GetPalette method code is analogous to that of the previous example. Now, when TWindowButton.Draw asks for color number 2, it is mapped to TNewWindow's color number 10, which is mapped to TApplication's color number 42, just as if it had been a TButton inserted into a TDialog. Note that I used CGrayWindow as the basis for CNewWindow's palette. Since a TButton is normally inserted into a TDialog, two of its colors (44 and 46) use a gray background. If you wanted to put buttons into cyan or blue windows, you would need to use the more general method of adding to TApplication's palette, as in the previous example. Last but not least, what about objects which can be instantiated with one of several palettes? TWindow is a good example of this type of object; you can have windows with blue, gray, or cyan color schemes. One of TWindow's fields (Palette) is used to indicate which color scheme GetPalette should return. TWindow.GetPalette might look something like this: function TWindow.GetPalette: PPalette; const PGray: string[Length (CGrayWindow)] = CGrayWindow; PCyan: string[Length (CCyanWindow)] = CCyanWindow; PBlue: string[Length (CBlueWindow)] = CBlueWindow; begin case Palette of wpGrayWindow: GetPalette = @PGray; wpCyanWindow: GetPalette = @PCyan; wpBlueWindow: GetPalette = @PBlue; end; end; You can use the same technique with any objects of your own devising. Well, that's about it for Turbo Vision palettes. Are you thoroughly confused yet? Just remember: figuring out what color a view is going to be drawn with is as simple as tracing the color mapping up the ownership hierarchy, until you reach the TApplication object. Please direct comments or suggestions to Steve Schafer [71121,1771].