/* * Copyright (c) 1987, 1988, 1989 Stanford University * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided * that the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Stanford not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. Stanford makes no representations about * the suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * STANFORD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. * IN NO EVENT SHALL STANFORD BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Implements pulldown menu classes. */ #include "istring.h" #include "pdmenu.h" #include #include #include #include #include #include #include #include #include /* Define the initial number of elements to allocate space for. */ static const int INITIALSIZE = 15; /* * PullDownMenuBar starts with no currently active or stored * activators although it allocates some initial space to store them. */ PullDownMenuBar::PullDownMenuBar () { cur = nil; sizeactivators = INITIALSIZE; numactivators = 0; activators = new PullDownMenuActivator*[sizeactivators]; } /* * Free storage allocated for the dynamic array. */ PullDownMenuBar::~PullDownMenuBar () { delete activators; } /* * Enter stores an interior activator and tells it it can get its * highlight painter from us. */ void PullDownMenuBar::Enter (PullDownMenuActivator* act) { if (numactivators == sizeactivators) { GrowActivators(); } activators[numactivators++] = act; act->SetHighlighterParent(this); } /* * Contains returns true if the child is one of the bar's activators * or one of the activators' commands. */ bool PullDownMenuBar::Contains (Interactor* child) { int i; for (i = 0; i < numactivators; i++) { if (activators[i] == child) { return true; } } for (i = 0; i < numactivators; i++) { if (activators[i]->Contains(child)) { return true; } } return false; } /* * MenuActive returns true if any of the bar's activators have opened * a menu. */ bool PullDownMenuBar::MenuActive () { return cur != nil; } /* * MenuShouldActivate returns true if any of the bar's activators * EXCEPT the given one have opened a menu. */ bool PullDownMenuBar::MenuShouldActivate (PullDownMenuActivator* act) { return cur != nil && cur != act; } /* * MenuActivate highlights the given activator, opens the activator's * menu, and stores it as the currently active activator. */ void PullDownMenuBar::MenuActivate (PullDownMenuActivator* act) { act->Highlight(true); act->Open(); cur = act; } /* * MenuDeactivate closes the currently active activator's menu, * unhighlights the activator, and stores no more active activators. */ void PullDownMenuBar::MenuDeactivate () { cur->Close(); cur->Highlight(false); cur = nil; } /* * GrowActivators increases the dynamic array's size to make room for * more activators to be stored. */ void PullDownMenuBar::GrowActivators () { PullDownMenuActivator** oldacts = activators; sizeactivators += INITIALSIZE/2; activators = new PullDownMenuActivator*[sizeactivators]; bcopy(oldacts, activators, numactivators * sizeof(PullDownMenuActivator*)); delete oldacts; } /* * PullDownMenuActivator stores the bar it belongs to and its text * label. It starts off with an empty menu and no stored commands * although it allocates some initial space to store the commands. It * must catch the same mouse button PullDownMenuCommand catches. */ PullDownMenuActivator::PullDownMenuActivator (PullDownMenuBar* b, const char* n) { b->Enter(this); bar = b; name = strdup(n ? n : ""); menu = new ShadowFrame; menu->SetCanvasType(CanvasSaveUnder); sizecommands = INITIALSIZE; numcommands = 0; commands = new PullDownMenuCommand*[sizecommands]; input = new Sensor(onoffEvents); input->CatchButton(DownEvent, LEFTMOUSE); input->CatchButton(UpEvent, LEFTMOUSE); } /* * Free storage allocated for members. */ PullDownMenuActivator::~PullDownMenuActivator () { delete name; delete menu; delete commands; } /* * SetMenu sets the menu's actual contents. */ void PullDownMenuActivator::SetMenu (Scene* box) { menu->Insert(box); } /* * Handle works together with the bar to determine whether any * activator has opened or should open a menu and accordingly tells * the bar to deactivate or activate an activator. */ void PullDownMenuActivator::Handle (Event& e) { switch (e.eventType) { case DownEvent: if (!bar->MenuActive()) { bar->MenuActivate(this); while (e.eventType != UpEvent) { Read(e); if (e.eventType == UpEvent) { bar->MenuDeactivate(); } if (bar->Contains(e.target)) { e.target->Handle(e); } } } break; case UpEvent: break; case OnEvent: if (bar->MenuShouldActivate(this)) { bar->MenuDeactivate(); bar->MenuActivate(this); } break; case OffEvent: break; } } /* * Enter stores an interior command and tells it it can get its * highlight painter from our bar like us too. */ void PullDownMenuActivator::Enter (PullDownMenuCommand* cmd) { if (numcommands == sizecommands) { GrowCommands(); } commands[numcommands++] = cmd; cmd->SetHighlighterParent(bar); } /* * Contains returns true if the command is one of the activator's * commands. */ bool PullDownMenuActivator::Contains (Interactor* cmd) { for (int i = 0; i < numcommands; i++) { if (commands[i] == cmd) { return true; } } return false; } /* * Open inserts the menu into the scene with the menu's top left * corner aligned with the activator's bottom left corner. */ void PullDownMenuActivator::Open () { World* world = GetWorld(); Coord l = 0; Coord b = 0; GetRelative(l, b, world); world->InsertPopup(menu, l, b, TopLeft); } /* * Close makes the activator's menu disappear. */ void PullDownMenuActivator::Close () { menu->Parent()->Remove(menu); } /* * Reconfig pads the activator's shape to accomodate its text label. * Basing padding on the font in use ensures the padding will change * proportionally with changes in the font's size. */ static const float WIDTHPAD = 1.0;/* fraction of font->Width(EM) */ static const float CMDHTPAD = 0.1;/* fraction of font->Height() */ static const float ACTHTPAD = 0.2;/* fraction of font->Height() */ static const char* EM = "m"; /* widest alphabetic character in any font */ void PullDownMenuActivator::Reconfig () { Highlighter::Reconfig(); Font* font = output->GetFont(); int xpad = round(WIDTHPAD * font->Width(EM)); int ypad = round(ACTHTPAD * font->Height()); shape->width = font->Width(name) + (2 * xpad); shape->height = font->Height() + (2 * ypad); shape->Rigid(shape->width - xpad, 0, 2 * ypad, 0); } /* * Redraw displays the text label. */ void PullDownMenuActivator::Redraw (Coord l, Coord b, Coord r, Coord t) { output->ClearRect(canvas, l, b, r, t); output->Text(canvas, name, name_x, name_y); } /* * Resize calculates the text label's position. */ void PullDownMenuActivator::Resize () { Font* font = output->GetFont(); name_x = max(0, (xmax - font->Width(name) + 1) / 2); name_y = (ymax - font->Height() + 1) / 2; } /* * GrowCommands increases the dynamic array's size to make room for * more commands to be stored. */ void PullDownMenuActivator::GrowCommands () { PullDownMenuCommand** oldcmds = commands; sizecommands += INITIALSIZE/2; commands = new PullDownMenuCommand*[sizecommands]; bcopy(oldcmds, commands, numcommands * sizeof(PullDownMenuCommand*)); delete oldcmds; } /* * PullDownMenuCommand stores the activator it belongs to and its text * labels. It catches only one mouse button to prevent the user from * accidentally executing a command upon another button's release. */ PullDownMenuCommand::PullDownMenuCommand (PullDownMenuActivator* a, const char* n, const char* k) { if (a != nil) { a->Enter(this); } activator = a; name = strdup(n ? n : ""); key = strdup(k ? k : ""); input = new Sensor(onoffEvents); input->CatchButton(UpEvent, LEFTMOUSE); } PullDownMenuCommand::~PullDownMenuCommand () { delete name; delete key; } /* * Highlight or unhighlight the command when the mouse passes * over and execute the command when the user releases the button * types its associated character (mapped by the program). */ void PullDownMenuCommand::Handle (Event& e) { switch (e.eventType) { case OnEvent: Highlight(true); break; case OffEvent: Highlight(false); break; case UpEvent: Execute(e); Highlight(false); break; case KeyEvent: activator->Highlight(true); activator->Flush(); Execute(e); activator->Highlight(false); break; } } /* * Execute carries out the command's purpose. */ void PullDownMenuCommand::Execute (Event&) { /* define it in your subclass */ } /* * Reconfig pads the command's shape to accomodate its text labels. */ void PullDownMenuCommand::Reconfig () { Highlighter::Reconfig(); Font* font = output->GetFont(); int xpad = round(WIDTHPAD * font->Width(EM)); int ypad = round(CMDHTPAD * font->Height()); shape->width = font->Width(name) + (2 * xpad); shape->width += font->Width(key) + (2 * xpad); shape->height = font->Height() + (2 * ypad); shape->Rigid(0, hfil, 0, 0); } /* * Redraw displays the text labels. */ void PullDownMenuCommand::Redraw (Coord l, Coord b, Coord r, Coord t) { output->ClearRect(canvas, l, b, r, t); output->Text(canvas, name, name_x, name_y); output->Text(canvas, key, key_x, key_y); } /* * Resize calculates the text labels' positions. */ void PullDownMenuCommand::Resize () { Font* font = output->GetFont(); int xpad = round(WIDTHPAD * font->Width(EM)); name_x = xpad; name_y = (ymax - font->Height() + 1) / 2; key_x = xmax - font->Width(key) - xpad; key_y = name_y; } /* * PullDownMenuDivider listens to no events so it will neither * highlight itself nor execute any command. */ PullDownMenuDivider::PullDownMenuDivider () : PullDownMenuCommand(nil, nil, nil) { Listen(noEvents); } /* * Redraw displays a horizontal line spanning the canvas. */ void PullDownMenuDivider::Redraw (Coord l, Coord b, Coord r, Coord t) { output->ClearRect(canvas, l, b, r, t); Coord hy = ymax / 2; output->Line(canvas, l, hy, r, hy); }