This article contains the following executables: DFPP01.ARC
I've been messing around with MIDI, something I used to avoid. MIDI is the Musical Instrument Digital Interface, a standard protocol that electronic instruments use to communicate among themselves and with sequencing computers. A few months ago I got a CD-ROM drive because SDKs are coming out now on CD-ROM disks. The hardware interface to the drive is also a PC sound card that includes a MIDI adapter. It's called a Sound Blaster Pro.
I am an old jazz pianist with a mild aversion to Yuppie jazz -- sterile, synthesized elevator stuff with saxophone players who sound alike and repeat themselves more often than an AK-47. I don't like being called a "keyboard player." I play the piano -- the real thing, the wooden kind with felt and strings. A couple of years ago in a weak moment, I bought a Yamaha Clavinova electric piano. It almost feels and sounds like a real one, and I can clamp on the headset and practice scales into the wee hours without jeopardizing my marriage or violating any neighborhood covenants. It's not the real thing, but it served its purpose.
The Yamaha has two MIDI sockets, in and out. I never paid much attention to them, but since my new Sound Blaster Pro has a cable that fits those sockets, I decided to see what it would do. The result is sometimes fun but mostly frustrating. It's almost the way I want it, but not quite right. If I could write a C program that would read and write that MIDI port, I could make it do my bidding.
With that in mind I bought a book that has C and Pascal code for programming the Sound Blaster. The book is called The Sound Blaster Book, by Axel Stolz (Abacus, 1992). It's a really good book with clear explanations of many of the technical aspects of music and sound reproduction. It explains everything about programming the Sound Blaster Pro except how to read and write the MIDI ports, the very thing I want to know. I have a number of other books that teach MIDI in a C environment, but everyone seems to assume that if you can copy a MIDI file to the instrument, that's all you need, and since the hardware comes with a couple of programs that will do that, there's no need to tell you how to write your own.
Using the sequencer software that came with the Sound Blaster, I got my system to do the following: I "record" a synthesized string-bass line for a given song and store it in a MIDI file. I record it by playing the bass notes on the lower part of the keyboard. When I play the song back, the thing uses the bass synthesizer and somehow kicks in the drum machine built into the keyboard device, from which I can select a tempo style. Music minus one, they used to call it. Karaoke, today. I can tell the sequencer to smooth out any erratic tempos or notes in what I recorded. I can record at a lower tempo to get it right and then play it back at the normal tempo. I can edit the notes on the screen to correct any clams. When I play the bass and drum parts back, I can manually play the piano along with them and have the tightest, most precise rhythm section a jazz pianist ever worked with, and it doesn't get stoned on the job, hit on the waitresses, come in late, or ask for more pay or better rooms.
That's great, and I'm having fun with it, but I want to control the program in real time just like I can with a live, breathing, drinking rhythm section. I want to decide on-the-fly to play another chorus, do a turnaround, put a tag on the end, change keys, play a substitution, or return to the bridge--all the things that improvising musicians can do with the structure of a song whenever the spirit moves them. To do that, I need to write a program that fetches different parts of the MIDI stream on command and writes the output port. I also have some ideas about improvising bass lines from a stream of chord symbols, perhaps even reading the keyboard input in real time and doing a translation. I've seen sequencers do that, but they never seem to understand all the chord inversions or contemporary voicings. To do those things the way I want, I need to read and write those MIDI ports. The documentation doesn't provide the port numbers, much less the protocols.
No doubt there is a subculture of MIDI mavens out there who already have these problems under their belts. Maybe some of them will fill in the holes created by my ignorance. I'll be researching this stuff further in the months to come and will report on its progress. Back to work.
This month fills out the D-Flat++ application environment with a menu system. The CUA menu architecture uses a menu bar across the top of the screen with labels to represent each of several pop-down menus. DF++ uses classes to define the menu bar and the pop-down menus. The applications program does not interact closely with these classes. The Application window takes care of that. The applications program must, however, define the characteristics of the menu system in a series of tables.
You will recall from code in the February "C Programming" column that the Application class includes a pointer to a MenuBar object. You build an application program by deriving a custom application window from the generic one. When you declare the custom application object, you include as a parameter a pointer to an array of MenuBarItem objects. The array defines the menu-bar items, which define the pop-down menus.
A D-Flat++ menu system is a hierarchy of objects--not a class inheritance hierarchy, but a hierarchical organization of related classes. At the top is the Application window, which points to a MenuBar object. The MenuBar object points to a list of MenuBarItem objects. Each MenuBarItem object builds a PopDown object. Each PopDown object contains a list of MenuSelection objects. Each MenuSelection object represents an action, which is usually a function call, a menu toggle, or a cascaded pop-down menu.
Listing One, page 130, is test.cpp, a very small D-Flat++ application that shows how an application program defines a menu and associates it with the Application window. The program builds an Application window with a single pop-down File menu. The first two commands, New and Open, do nothing. The Exit command terminates the program.
This 45-line program is self contained. It fully describes its menus and application environment. The myAppl class defines the application, which has a menu, a title, and three functions executed by menu commands.
Observe the way the program defines menus. It declares three MenuSelection objects. These are the selections that users are given. They will be distributed among the pop-down menus. Their proximity to one another here is coincidental. You can change their distribution on the menus without touching these declarations.
Next comes an array of pointers to the MenuSelection objects. Each such array defines a pop-down menu, in this case the only one in the program. The address of the SelectionSeparator object specifies that DF++ should display a horizontal separator line between selections. The array is NULL terminated.
The array of MenuBarItem objects defines the menu bar. Each entry in the array specifies a menu-bar label and a pointer to the MenuSelection array that defines the associated pop-down menu. The array is terminated by an entry constructed with a NULL pointer argument.
You can see that the menu definition uses two different techniques for defining the menu bar and the pop-down menus. The array of MenuBarItems is an array of objects. The arrays of MenuSelection items are arrays of pointers to externally declared MenuSelection objects. By declaring those objects outside of the actual menu definitions, you give each command its own identity and can send messages to it, such as to tell it to disable itself, report its current toggle state, and so on. If the objects were arrayed themselves in the menus, their identities would be through subscripts into the specific menus and would not be as easy to move around when you modified the design.
The constructor for the Application window class uses the pointer to the MenuBarItem array to construct an instance of the MenuBar class defined in menubar.h (Listing Two, page 130). Listing Two also defines the MenuBarItem class. The array of MenuBarItem objects defines the contents of the MenuBar object. Listing Three, page 130, is menubar.cpp, which contains the member functions for the MenuBar and MenuBarItem classes.
When the Application window displays, so does the menu bar. When the Application window gets resized, the MenuBar window receives the Parent-Sized message and adjusts its own size to fit the new size of the Application window.
When any document window in an application does not intercept and process a keystroke, the base DFWindow class passes the keystroke to the parent of the window that rejected the key. The top ancestor of all windows is the reigning Application window, so it ultimately gets any unintercepted keystrokes. When the Application window receives a keystroke that it does not want to process, it passes the keystroke to the MenuBar object. The MenuBar object, therefore, gets all the keystrokes that no other window wants.
The MenuBar class's Keyboard function looks first to see if the keystroke is any of the accelerator keys assigned to menu commands within the PopDown objects. An accelerator key is a keystroke that the user may employ to execute a menu command even though the menu is not popped down. You'll see later how PopDown objects are built. Next, the MenuBar object tests to see if the keystroke is one of the menu bar's shortcut keys. A shortcut key is a highlighted letter or number in a menu selection's label. The user may press Alt plus the highlighted character to select the shortcut key on a menu bar. (Alt is not needed to select the shortcut key on a pop-down menu.) Selecting the menu's shortcut key in this fashion selects and pops down the associated menu.
The MenuBar class's LeftButton message can select a pop-down menu, too. If the mouse coordinates are within the menu label's display, the MenuBar object will pop down the menu.
Each MenuBarItem object represents one pop-down menu and includes the address of an array of MenuSelection objects that define the characteristics of each selection on the menu. Most selections will consist of a label and the address of a function to be executed when the user chooses the selection. Those functions are member functions of the custom application class that you derive from the D-Flat++ Application class.
Listing Four (page 132) is menusel.h, the header file that defines the MenuSelection class, and Listing Five (page 132) is menusel.cpp. Menu definitions for an application consist of arrays of pointers to MenuSelection objects, as shown in Listing One. You can construct a MenuSelection object with a label and a function, or with various combinations of label, function, accelerator key, and active/inactive state conditions. MenuSelection objects can also be separator tokens to display a line between the normal selections on the menu. One constructor allows you to specify a toggle value of On or Off, which identifies the selection as one that can be toggled. A toggled menu selection maintains an On/Off state depending on the most recent selection by the user. It displays a check mark next to its label when it is in the On state. The application program can query the current condition of a toggled menu selection's On/Off state even when the pop-down menu is not active.
One constructor for the MenuSelection class allows you to specify the address of another array of MenuSelection objects. This format identifies a cascading menu selection. When the user selects the item, DF++ pops down a cascading menu as represented by the second array. Cascading menus may themselves contain cascading menus.
The MenuBar constructor iterates through the list of MenuBarItem objects and builds the menu bar by building a string of menu labels. This string, which is the menu-bar display, has a text label for each pop-down menu. The constructor also instantiates each of the pop-down menus by building PopDown objects, passing the address of the array of MenuSelection objects associated with each MenuBarItem object. The pop-down windows do not display at this time--they are simply constructed. The MenuBar object will display each of the pop-down windows only when the user selects them.
Listing Six (page 132) and Listing Seven (page 132) are popdown.h and popdown.cpp, respectively. They define the PopDown class, which is derived from the ListBox class I described last month. A PopDown object has some unique behavior, however. Each pop-down window adjusts its size and position to match its contents and the position of its parent. A noncascading pop-down menu is positioned just below the menu-bar label that selects it. If that position puts the menu offscreen, the menu will self-adjust to stay onscreen. A cascading menu positions itself adjacent to the menu selection that selected the cascade, also adjusting itself to stay onscreen.
A pop-down menu's borders are constant. They do not change to reflect an in-focus state like other list boxes do. The Paint message recognizes toggled selections and displays or clears the check mark depending on the state of the toggle.
The Keyboard message exhibits unique behavior, too. The text of a pop-down menu never exceeds the height of the window, so scrolling never occurs. The selection cursor wraps from bottom to top and vice versa. The forward and back arrow keys close the current menu and cause the next right or left pop-down menu to be opened.
Selection of a menu item occurs when the user selects the item and presses the Enter key, a matching accelerator key, or a matching shortcut key while the menu is open. Selection also occurs if the user presses and releases the left mouse button on a menu selection. The button release makes the selection.
A menu selection--implemented by the Choose method--does one of the following things: If the menu is not enabled, the selection sounds the audible alarm; it toggles the selection's On/Off state if the selection is a toggle; it opens a cascaded pop-down menu if the selection is a cascading selection; and it calls the Application window's member function if one is assigned to the selection. If a function is assigned, the program closes the pop-down menu and any higher cascading menus. Menuselection application functions execute with the menus all closed down. A toggle selection may include a command function as well.
The last part of the menu system is the control menu, implemented in Listing Eight, page 138, ctlmenu.cpp. The CUA control menu, sometimes called the "system menu," contains one or more of a fixed set of generic window commands. These commands are: Restore, Move, Size, Minimize, Maximize, and Close. The user can use this menu to perform these operations. The user opens the window by clicking the control box in the upper-left corner of the window or by pressing Alt+Spacebar for an Application window or dialog box and Alt+Hyphen for a document window. The DFWindow class's Keyboard method intercepts these keystrokes and calls the OpenCtlMenu function, which customizes the control menu according to the attributes of the window. For example, if the window may be minimized or maximized, the control menu will have a Restore command. If the window is currently in either of those conditions, the Restore command will be enabled. The other commands are similarly customized.
D-Flat++ is moving along. The current version is not a full CUA package but it is growing, and there is enough of an implementation to give you an idea of how it will work and how it will differ from D-Flat. You can download DF++ from the CompuServe DDJ forum or from M&T Online. You can also get it by sending a stamped, self-addressed diskette mailer and a formatted diskette to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402. I'll include the latest version of D-Flat as well. The software is free, but if you wish, include a dollar for my Careware charity, the Brevard County Food Bank.
Copyright © 1993, Dr. Dobb's JournalD-Flat++ Menus
A Sample Application Program
The MenuBar Class
MenuBar Keystrokes
The MenuBarItem Class
The MenuSelection Class
Building the Menu Bar
The PopDown Class
The Control Menu
How to Get the Source Code
_C PROGRAMMING COLUMN_
by Al Stevens
[LISTING ONE]
// ------------- test.cpp
#include "dflatpp.h"
extern MenuBarItem aMenu[];
// ------- application definition
class myAppl : public Application {
public:
myAppl() : Application("Hello",aMenu) {}
// ----- menu command functions
void DoNew() {}
void DoOpen() {}
void DoExit() { CloseWindow(); }
};
// --------- MenuSelection objects
MenuSelection
NewCmd ("~New", (void (DFWindow::*)()) &myAppl::DoNew ),
OpenCmd ("~Open", (void (DFWindow::*)()) &myAppl::DoOpen ),
ExitCmd ("E~xit Alt+F4", (void (DFWindow::*)()) &myAppl::DoExit, ALT_F4);
// --------- File menu definition
MenuSelection *File[] = {
&NewCmd,
&OpenCmd,
&SelectionSeparator,
&ExitCmd,
NULL
};
// --------- menu bar definition
MenuBarItem aMenu[] = {
MenuBarItem( "~File", File ),
MenuBarItem( NULL )
};
void main()
{
myAppl *aWnd = new myAppl;
while (desktop.DispatchEvents())
;
delete aWnd;
}
[LISTING TWO]
// -------- menubar.h
#ifndef MENUBAR_H
#define MENUBAR_H
#include "textbox.h"
#include "popdown.h"
class MenuBarItem {
public:
String *title; // menu bar selection label
int x1; // 1st label position on bar
int x2; // last " " " "
MenuSelection **ms; // popdown selection list
PopDown *popdown; // popdown window
void (*menuprep)(); // menu prep function
MenuBarItem(char *Title, MenuSelection **Ms = NULL,
void (*MenuPrep)() = NULL);
~MenuBarItem() { if (title) delete title; }
};
class MenuBar : public TextBox {
MenuBarItem *menuitems; // list of popdowns
int menucount; // count of popdowns
int selection; // current selection on the bar
Bool ispoppeddown; // True = a menu is down
DFWindow *oldfocus; // previous focus
void SetColors();
void Select();
Bool AcceleratorKey(int key);
Bool ShortCutKey(int key);
public:
MenuBar(MenuBarItem *MenuItems, DFWindow *par);
~MenuBar();
// -------- menubar API messages
void Keyboard(int key);
void LeftButton(int mx, int my);
Bool SetFocus();
void ResetFocus();
void Paint();
void Select(int sel);
void SetSelection(int sel);
void ParentSized(int xdif, int ydif);
};
#endif
[LISTING THREE]
// ------------- menubar.cpp
#include <ctype.h>
#include "desktop.h"
#include "menubar.h"
#include "menusel.h"
// -------- construct a menubar item
MenuBarItem::MenuBarItem(char *Title, MenuSelection **Ms,void (*MenuPrep)())
{
if (Title != NULL)
title = new String(Title);
else
title = NULL;
ms = Ms;
menuprep = MenuPrep;
popdown = NULL;
}
// -------- construct a menubar
MenuBar::MenuBar( MenuBarItem *MenuItems, DFWindow *par) :
TextBox( par->ClientLeft(), par->ClientTop()-1,
1, par->ClientWidth(), par)
{
windowtype = MenubarWindow;
menuitems = MenuItems;
SetAttribute(NOCLIP);
SetColors();
selection = -1;
ispoppeddown = False;
MenuBarItem *menu = menuitems;
menucount = 0;
oldfocus = NULL;
int off = 2;
SetTextLength(desktop.screen().Width()*2);
while (menu->title != NULL) {
int len = menu->title->Strlen()-1;
menu->x1 = off;
menu->x2 = off+len-1;
off += len+2;
String ttl(" ");
ttl += *(menu->title);
AddText(ttl);
int n = text->Strlen()-1;
(*text)[n] = '\0';
menu->popdown = new PopDown(this, menu->ms);
menu++;
menucount++;
}
}
// -------- menubar destructor
MenuBar::~MenuBar()
{
MenuBarItem *menu = menuitems;
while (menu->title != NULL) {
delete menu->popdown;
menu++;
}
TextBox::CloseWindow();
}
// -------- set the fg/bg colors for the window
void MenuBar::SetColors()
{
colors.fg = BLACK;
colors.bg = LIGHTGRAY;
colors.sfg = BLACK;
colors.sbg = CYAN;
colors.ffg = BLACK;
colors.fbg = LIGHTGRAY;
colors.hfg = BLACK;
colors.hbg = LIGHTGRAY;
shortcutfg = RED;
}
// ---- menubar gets the focus
Bool MenuBar::SetFocus()
{
if (oldfocus == NULL)
if (desktop.InFocus() != NULL)
if (desktop.InFocus()->State() != ISCLOSING)
oldfocus = desktop.InFocus();
return TextBox::SetFocus();
}
// ---- menubar loses the focus
void MenuBar::ResetFocus()
{
if (!ispoppeddown) {
SetSelection(-1);
oldfocus = NULL;
}
TextBox::ResetFocus();
}
// -------- paint the menubar
void MenuBar::Paint()
{
WriteShortcutLine(0, colors.fg, colors.bg);
if (selection != -1) {
int x = menuitems[selection].x1;
int len = menuitems[selection].x2-x+2;
String sel = text->mid(len, x+selection);
DisplayShortcutField(sel,x,0,colors.sfg,colors.sbg);
}
}
// ------- left mouse button is pressed
void MenuBar::LeftButton(int mx, int)
{
mx -= Left();
MenuBarItem *menu = menuitems;
int sel = 0;
while (menu->title != NULL) {
if (mx >= menu->x1 && mx <= menu->x2) {
if (selection != sel || !ispoppeddown) {
if (ispoppeddown) {
PopDown *pd = menuitems[selection].popdown;
if (pd->isOpen())
pd->CloseMenu(False);
}
Select(sel);
}
return;
}
sel++;
menu++;
}
if (selection == -1)
SetSelection(0);
}
void MenuBar::SetSelection(int sel)
{
selection = sel;
Paint();
}
// ----- programmed selection
void MenuBar::Select(int sel) {
selection = sel;
Select();
}
// ------- user selection
void MenuBar::Select()
{
Paint();
ispoppeddown = True;
MenuBarItem &mb = *(menuitems+selection);
int lf = Left() + mb.x1;
int tp = Top()+1;
if (mb.menuprep != NULL)
(*mb.menuprep)();
mb.popdown->OpenMenu(lf, tp);
}
// ------ test for popdown accelerator key
Bool MenuBar::AcceleratorKey(int key)
{
MenuBarItem *menu = menuitems;
while (menu->title != NULL) {
PopDown *pd = menu->popdown;
if (pd->AcceleratorKey(key))
return True;
menu++;
}
return False;
}
// ------ test for menubar shortcut key
Bool MenuBar::ShortCutKey(int key)
{
int altkey = desktop.keyboard().AltConvert(key);
MenuBarItem *menu = menuitems;
int sel = 0;
while (menu->title != NULL) {
int off = menu->title->FindChar(SHORTCUTCHAR);
if (off != -1) {
String &cp = *(menu->title);
int c = cp[off+1];
if (tolower(c) == altkey) {
SetFocus();
Select(sel);
return True;
}
}
sel++;
menu++;
}
return False;
}
// -------- keystroke while menubar has the focus
void MenuBar::Keyboard(int key)
{
if (AcceleratorKey(key))
return;
if (!ispoppeddown && ShortCutKey(key))
return;
switch (key) {
case F10:
if (ispoppeddown)
break;
if (this != desktop.InFocus()) {
if (selection == -1)
selection = 0;
SetFocus();
break;
}
// ------ fall through
case ESC:
ispoppeddown = False;
SetSelection(-1);
if (oldfocus != NULL)
oldfocus->SetFocus();
else
parent->SetFocus();
break;
case FWD:
selection++;
if (selection == menucount)
selection = 0;
if (ispoppeddown)
Select();
else
Paint();
break;
case BS:
if (selection == 0)
selection = menucount;
--selection;
if (ispoppeddown)
Select();
else
Paint();
break;
case '\r':
if (selection != -1)
Select();
break;
case ALT_F6:
TextBox::Keyboard(key);
break;
default:
break;
}
}
// ---- resize the menubar when the application window resizes
void MenuBar::ParentSized(int xdif, int)
{
Size(Right()+xdif, Bottom());
}
[LISTING FOUR]
// ------- menusel.h
#ifndef MENUSEL_H
#define MENUSEL_H
#include <stdio.h>
#include "strings.h"
#include "dfwindow.h"
enum MenuType {
NORMAL,
TOGGLE,
CASCADER,
SEPARATOR
};
enum Toggle { Off, On };
class DFWindow;
class PopDown;
#define NULLFUNC (void (DFWindow::*)())NULL
class MenuSelection {
String *label; // selection label
void (DFWindow::*cmdfunction)(); // selection function
MenuType type; // NORMAL, TOGGLE,
// CASCADER, SEPARATOR
Bool isenabled; // True = enabled, False = disabled
int accelerator; // accelerator key
PopDown *cascade; // cascaded menu window
MenuSelection **cascaders; // cascaded menu selection list
Toggle toggle; // On or Off
void NullSelection(); // Build a null selection
friend PopDown;
void CommonConstructor( char *Label,
int Accelerator,
void (DFWindow::*CmdFunction)(),
Bool Active,
MenuType Type,
Toggle Tgl,
MenuSelection **Cascaders = NULL);
public:
MenuSelection( char *Label,
void (DFWindow::*CmdFunction)() = 0,
int Accelerator=0,
Bool Active=True );
MenuSelection( char *Label,
void (DFWindow::*CmdFunction)(),
Toggle Tgl,
int Accelerator=0,
Bool Active=True );
MenuSelection( char *Label,
MenuSelection **Cascaders,
int Accelerator=0,
Bool Active=True );
MenuSelection(MenuType Type);
Bool isEnabled() { return isenabled; }
void Enable() { isenabled = True; }
void Disable() { isenabled = False; }
void SetToggle() { toggle = On; }
void ClearToggle() { toggle = Off; }
void InvertToggle() { toggle = toggle == On ? Off : On; }
Bool isToggled() { return (Bool) (toggle == On); };
Type() { return type; }
};
extern MenuSelection SelectionSeparator;
extern MenuSelection SelectionTerminator;
#endif
[LISTING FIVE]
// ------- menusel.cpp
#include <string.h>
#include "menusel.h"
MenuSelection SelectionSeparator(SEPARATOR);
void MenuSelection::NullSelection()
{
label = NULL;
cmdfunction = NULL;
type = NORMAL;
cascade = NULL;
isenabled = True;
accelerator = 0;
cascade = NULL;
toggle = Off;
}
void MenuSelection::CommonConstructor(
char *Label,
int Accelerator,
void (DFWindow::*CmdFunction)(),
Bool Active,
MenuType Type,
Toggle Tgl,
MenuSelection **Cascaders)
{
NullSelection();
if (Label != NULL)
label = new String(Label);
accelerator = Accelerator;
cmdfunction = CmdFunction;
isenabled = Active;
type = Type;
toggle = Tgl;
cascaders = Cascaders;
}
MenuSelection::MenuSelection( char *Label,
void (DFWindow::*CmdFunction)(),
int Accelerator, Bool Active )
{
CommonConstructor(Label, Accelerator, CmdFunction, Active, NORMAL, Off);
}
MenuSelection::MenuSelection( char *Label,
void (DFWindow::*CmdFunction)(),
Toggle Tgl, int Accelerator, Bool Active)
{
CommonConstructor(Label, Accelerator, CmdFunction, Active, TOGGLE, Tgl);
}
MenuSelection::MenuSelection(char *Label,
MenuSelection **Cascaders,
int Accelerator, Bool Active )
{
CommonConstructor(Label, Accelerator, NULL,
Active, CASCADER, Off, Cascaders);
}
MenuSelection::MenuSelection(MenuType Type)
{
NullSelection();
type = Type;
}
[LISTING SIX]
// -------- popdown.h
#ifndef POPDOWN_H
#define POPDOWN_H
#include "desktop.h"
#include "listbox.h"
const unsigned char LEDGE = '\xc3';
const unsigned char REDGE = '\xb4';
const unsigned char CASCADEPOINTER = '\x10';
inline unsigned char CheckMark()
{
return desktop.screen().Height() == 25 ? 251 : 4;
}
class MenuSelection;
class MenuBar;
class PopDown : public ListBox {
MenuSelection **selections; // array of selections
Bool isopen; // True = menu is open
Bool iscascaded; // True = menu is cascaded
int menuwidth; // width of menu
int menuheight; // height of menu
void BuildMenuLine(int sel);
void MenuDimensions();
void SetColors();
void DisplayMenuLine(int lno);
Bool ShortCutKey(int key);
protected:
void ClearSelection();
public:
PopDown(DFWindow *par, MenuSelection **Selections = NULL)
: ListBox(5, 5, par)
{ selections = Selections; OpenWindow(); }
virtual ~PopDown()
{ if (windowstate != CLOSED) CloseWindow(); }
// -------- listbox API messages
void OpenWindow();
void CloseWindow();
void OpenMenu(int left, int top);
void CloseMenu(Bool SendESC = False);
void Show();
void Paint();
void Border();
void Keyboard(int key);
void ShiftChanged(int sk);
void ButtonReleased(int mx, int my);
void LeftButton(int mx, int my);
void DoubleClick(int mx, int my);
void Choose();
void SetSelection(int sel);
Bool isOpen() { return isopen; }
Bool &isCascaded() { return iscascaded; }
Bool AcceleratorKey(int key);
Bool ParentisMenu(DFWindow &wnd);
Bool ParentisMenu() { return ParentisMenu(*this); }
};
#endif
[LISTING SEVEN]
// ------------- popdown.cpp
#include <ctype.h>
#include "desktop.h"
#include "popdown.h"
#include "menusel.h"
// --------- create a popdown menu
void PopDown::OpenWindow()
{
windowtype = PopdownWindow;
if (windowstate == CLOSED)
ListBox::OpenWindow();
SetAttribute(BORDER | SHADOW | SAVESELF | NOCLIP);
selection = 0;
DblBorder = False;
isopen = False;
SetColors();
iscascaded = False;
if (selections != NULL) {
MenuDimensions();
SetTextLength(menuwidth * menuheight);
for (int i = 0; i < menuheight; i++) {
MenuSelection &ms = **(selections+i);
BuildMenuLine(i);
if (ms.type == CASCADER) {
ms.cascade = new PopDown(this, ms.cascaders);
ms.cascade->isCascaded() = True;
}
}
rect.Right() = rect.Left() + menuwidth;
rect.Bottom() = rect.Top() + menuheight + 1;
}
}
// ---- shut down a popdown menu
void PopDown::CloseWindow()
{
if (selections != NULL) {
// --- delete all cascader popdowns
for (int i = 0; selections[i]; i++) {
MenuSelection &ms = *selections[i];
if (ms.type == CASCADER && ms.cascade != NULL)
delete ms.cascade;
}
}
ListBox::CloseWindow();
}
// ------- pop down the menu
void PopDown::OpenMenu(int left, int top)
{
Rect rc(0, 0, desktop.screen().Width()-1,
desktop.screen().Height()-1);
DFWindow *Wnd = parent;
while (Wnd != NULL && Wnd->WindowType() == PopdownWindow)
Wnd = Wnd->Parent();
if (Wnd != NULL && (Wnd = Wnd->Parent()) != NULL) {
Rect rc = Wnd->ClientRect();
left = min(max(left, rc.Left()), rc.Right() - ClientWidth());
top = min(max(top, rc.Top()), rc.Bottom() - ClientHeight());
}
left = min(max(left, rc.Left()), rc.Right()-ClientWidth()-1);
top = min(max(top, rc.Top()), rc.Bottom()-ClientHeight()-1);
isopen = True;
Move(left, top);
CaptureFocus();
Paint(); // in case a command attribute changed
}
// ---------- deactivate the popdown menu
void PopDown::CloseMenu(Bool SendESC)
{
if (isopen) {
// ------- close any open cascaded menus
PopDown *Wnd = (PopDown *)first;
while (Wnd != NULL) {
Wnd->CloseMenu();
Wnd = (PopDown *) (Wnd->next);
}
Hide();
isopen = False;
ReleaseFocus();
if (parent && !iscascaded && SendESC)
parent->Keyboard(ESC);
}
}
void PopDown::Show()
{
if (isopen)
ListBox::Show();
}
// -------- build a menu line
void PopDown::BuildMenuLine(int sel)
{
int wd = menuwidth;
String ln;
if (selections[sel]->type == SEPARATOR)
ln = String(--wd, LINE);
else {
ln = String(" ");
ln += *(selections[sel]->label);
int r = wd-ln.Strlen();
ln += String(r, ' ');
if (selections[sel]->type == CASCADER)
ln[wd-1] = CASCADEPOINTER;
}
AddText(ln);
}
// -------- compute menu width
void PopDown::MenuDimensions()
{
int txlen = 0;
for (int i = 0; selections[i] != NULL; i++) {
if (selections[i]->type != SEPARATOR) {
int lblen = (selections[i]->label)->Strlen()-1;
txlen = max(txlen, lblen);
}
}
menuwidth = txlen+4;
menuheight = i;
}
// -------- set the fg/bg colors for the window
void PopDown::SetColors()
{
colors.fg = BLACK;
colors.bg = CYAN;
colors.sfg = BLACK;
colors.sbg = LIGHTGRAY;
colors.ffg = BLACK;
colors.fbg = CYAN;
colors.hfg = DARKGRAY; // Inactive FG
colors.hbg = CYAN; // Inactive FG
shortcutfg = RED;
}
// ------ display a menu line
void PopDown::DisplayMenuLine(int lno)
{
if (isopen) {
int fg, bg;
int isActive = selections[lno]->isEnabled();
int sfg = shortcutfg;
if (lno == selection) {
fg = colors.sfg;
bg = colors.sbg;
}
else if (isActive) {
fg = colors.fg;
bg = colors.bg;
}
else {
fg = colors.hfg;
bg = colors.hbg;
}
if (!isActive)
shortcutfg = fg;
WriteShortcutLine(lno, fg, bg);
shortcutfg = sfg;
}
}
// ------ set no selection current
void PopDown::ClearSelection()
{
if (selection != -1) {
int sel = selection;
selection = -1;
DisplayMenuLine(sel);
}
}
// ------ set a current menu selection
void PopDown::SetSelection(int sel)
{
ClearSelection();
if (sel >= 0 && sel < wlines) {
selection = sel;
DisplayMenuLine(sel);
}
}
// ---------- paint the menu
void PopDown::Paint()
{
if (text == NULL)
ListBox::Paint();
else {
for (int i = 0; i < wlines; i++) {
if (selections[i]->type == TOGGLE) {
char *cp = TextLine(i);
if (selections[i]->toggle == On)
*cp = CheckMark();
else
*cp = ' ';
}
DisplayMenuLine(i);
}
}
}
// --------- paint the menu's border
void PopDown::Border()
{
if (isopen && isVisible()) {
int fg = colors.ffg;
int bg = colors.fbg;
int rt = Width()-1;
ListBox::Border();
for (int i = 0; i < wlines; i++) {
if (selections[i]->type == SEPARATOR) {
WriteWindowChar(LEDGE, 0, i+1, fg, bg);
WriteWindowChar(REDGE, rt, i+1, fg, bg);
}
}
}
}
// ------- test for a menu selection accelerator key
Bool PopDown::AcceleratorKey(int key)
{
for (int i = 0; i < wlines; i++) {
MenuSelection &ms = **(selections+i);
if (key == ms.accelerator) {
SetSelection(i);
Choose();
return True;
}
}
return False;
}
// ------- test for a menu selection shortcut key
Bool PopDown::ShortCutKey(int key)
{
key = tolower(key);
for (int i = 0; i < wlines; i++) {
MenuSelection &ms = **(selections+i);
int off = ms.label->FindChar(SHORTCUTCHAR);
if (off != -1) {
String &cp = *ms.label;
int c = cp[off+1];
if (key == tolower(c)) {
SetSelection(i);
Choose();
return True;
}
}
}
return False;
}
// ----- keystroke while menu is popped down
void PopDown::Keyboard(int key)
{
if (AcceleratorKey(key))
return;
if (ShortCutKey(key))
return;
switch (key) {
case UP:
if (selection == 0) {
SetSelection(wlines-1);
return;
}
if (selections[selection-1]->type == SEPARATOR) {
SetSelection(selection-2);
return;
}
break;
case DN:
if (selection == wlines-1) {
SetSelection(0);
return;
}
if (selections[selection+1]->type == SEPARATOR) {
SetSelection(selection+2);
return;
}
break;
case ESC:
CloseMenu(ParentisMenu());
return;
case FWD:
case BS:
CloseMenu();
if (parent != NULL) {
parent->Keyboard(key);
return;
}
break;
default:
break;
}
ListBox::Keyboard(key);
}
// ----- shift key status changed
void PopDown::ShiftChanged(int sk)
{
if (sk & ALTKEY)
CloseMenu(ParentisMenu());
}
// ---------- Left mouse button was clicked
void PopDown::LeftButton(int mx, int my)
{
if (ClientRect().Inside(mx, my)) {
if (my != prevmouseline) {
int y = my - ClientTop();
if (selections[y]->type != SEPARATOR)
SetSelection(y);
}
}
else if (!rect.Inside(mx, my)) {
if (parent && my == parent->Bottom())
parent->LeftButton(mx, my);
}
prevmouseline = my;
prevmousecol = mx;
}
// ---------- Left mouse button was double-clicked
void PopDown::DoubleClick(int mx, int my)
{
if (!rect.Inside(mx, my)) {
CloseMenu();
if (parent)
parent->DoubleClick(mx, my);
}
}
// ---------- Left mouse button was released
void PopDown::ButtonReleased(int mx, int my)
{
if (ClientRect().Inside(mx, my)) {
if (prevmouseline == my && prevmousecol == mx)
if (selections[my-ClientTop()]->type != SEPARATOR)
Choose();
}
else if (!rect.Inside(mx, my)) {
DFWindow *Wnd = desktop.inWindow(mx, my);
if (!(Wnd == parent && my == Top()-1 &&
mx >= Left() && mx <= Right())) {
CloseMenu(ParentisMenu());
if (Wnd != NULL && Wnd != desktop.InFocus())
Wnd->SetFocus();
}
}
}
// --------- user chose a menu selection
void PopDown::Choose()
{
MenuSelection &ms = *selections[selection];
if (ms.isEnabled()) {
if (ms.type == CASCADER && ms.cascade != NULL)
// -------- cascaded menu
ms.cascade->OpenMenu(Right(), Top()+selection);
else {
if (ms.type == TOGGLE) {
// ---- toggle selection
ms.InvertToggle();
char *cp = TextLine(selection);
if (*cp == CheckMark())
*cp = ' ';
else
*cp = CheckMark();
DisplayMenuLine(selection);
}
if (ms.cmdfunction != NULL) {
// ---- there is a function associated
DFWindow *wnd = (DFWindow *)this;
// --- close all menus
while (wnd &&
wnd->WindowType() == PopdownWindow) {
((PopDown *)wnd)->CloseMenu();
wnd = wnd->Parent();
}
if (wnd && wnd->WindowType() == MenubarWindow){
wnd->Keyboard(ESC);
wnd = wnd->Parent();
}
if (wnd)
// ---- execute the function
(wnd->*ms.cmdfunction)();
}
}
}
else
desktop.speaker().Beep(); // disabled selection
}
inline Bool isMenu(DFWindow *wnd)
{
if (wnd != NULL) {
WndType wt = wnd->WindowType();
return (Bool) (wt==MenubarWindow || wt==PopdownWindow);
}
return False;
}
// ----- test for the parent as menu or menubar
Bool PopDown::ParentisMenu(DFWindow &wnd)
{
return isMenu(wnd.Parent());
}
[LISTING EIGHT]
// --------------- ctlmenu.cpp
#include "dflatpp.h"
#include "frame.h"
MenuSelection RestoreCmd ("~Restore", &DFWindow::Restore);
MenuSelection MoveCmd ("~Move", &DFWindow::CtlMenuMove);
MenuSelection SizeCmd ("~Size", &DFWindow::CtlMenuSize);
MenuSelection MinimizeCmd ("Mi~nimize", &DFWindow::Minimize);
MenuSelection MaximizeCmd ("Ma~ximize", &DFWindow::Maximize);
MenuSelection CloseDocCmd ("~Close [Ctrl+F4]",&DFWindow::CloseWindow, CTRL_F4);
MenuSelection CloseApCmd ("~Close [Alt+F4]",&DFWindow::CloseWindow, ALT_F4);
MenuSelection *ControlMenu[8];
MenuBarItem CtlMenu[] = {
MenuBarItem( "", ControlMenu ),
MenuBarItem( NULL, NULL )
};
void DFWindow::OpenCtlMenu()
{
if (ctlmenu != NULL)
delete ctlmenu;
int mn = 0;
if (attrib & (MINBOX | MAXBOX))
ControlMenu[mn++] = &RestoreCmd;
if (attrib & MOVEABLE)
ControlMenu[mn++] = &MoveCmd;
if (attrib & SIZEABLE)
ControlMenu[mn++] = &SizeCmd;
if (attrib & MINBOX)
ControlMenu[mn++] = &MinimizeCmd;
if (attrib & MAXBOX)
ControlMenu[mn++] = &MaximizeCmd;
if (mn != 0)
ControlMenu[mn++] = &SelectionSeparator;
if (Parent())
ControlMenu[mn++] = &CloseDocCmd;
else
ControlMenu[mn++] = &CloseApCmd;
ControlMenu[mn] = NULL;
MinimizeCmd.Disable();
MaximizeCmd.Disable();
RestoreCmd.Disable();
SizeCmd.Disable();
MoveCmd.Disable();
switch (windowstate) {
case ISRESTORED:
if (attrib & MINBOX)
MinimizeCmd.Enable();
if (attrib & MAXBOX)
MaximizeCmd.Enable();
MoveCmd.Enable();
SizeCmd.Enable();
break;
case ISMINIMIZED:
RestoreCmd.Enable();
MoveCmd.Enable();
break;
case ISMAXIMIZED:
RestoreCmd.Enable();
if (attrib & MINBOX)
MinimizeCmd.Enable();
break;
}
ctlmenu = new PopDown(this, ControlMenu);
ctlmenu->OpenMenu(Left()+1, Top()+1);
}
void DFWindow::DeleteCtlMenu()
{
if (ctlmenu != NULL)
delete ctlmenu;
ctlmenu = NULL;
}
void DFWindow::CtlMenuMove()
{
desktop.mouse().SetPosition(Left(), Top());
new Frame(this, Left());
}
void DFWindow::CtlMenuSize()
{
desktop.mouse().SetPosition(Right(), Bottom());
new Frame(this);
}