_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 #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 #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 #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 #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); }