// // Copyright (C) 1996 Ben Ross // // You may distribute under the terms of the GNU General Public // License as specified in the COPYING file. // // $Id: XeMenu.C,v 1.6 2001/07/29 21:34:00 ben Exp $ #include #include #include #include #include #include #include #include #include #include #ifdef _SGI_SOURCE #include #endif extern XeObjectTable gObjectTable; //////////////////////////////////////////////////////////////////////////////// // // Implementation of menu items in the menu's list of items. // int XeMenuItem::_hiliteWidth = 2; #define GAP 8 XeMenuItem::XeMenuItem(XeMenu* menu) { _menu = menu; //_next = NULL; //_prev = NULL; memset(&_rect, 0, sizeof(XRectangle)); _disabled = FALSE; _index = -1; _cmdKey = 0; _modifiers = 0; } XeMenuItem::~XeMenuItem(void) { } void XeMenuItem::getResources(void) { _menu->getResStrings(); } // // $$$ big bug! // Menus don't resize properly in certain situations // Need to have a separate widest command string and widest label and the // width of the menu worked out from the combination of the two, since the // menu must be at least as wide as the combined width of both widths and // they may be in completely separate menu items. // XeTextItem::XeTextItem(XeMenu* menu, char* name, char* label, char* klabel, KeySym cmdKey, uint modifiers) : XeMenuItem(menu) { char* fontname = "-adobe-helvetica-bold-r-normal-*-12-*-*-*-*-*-*-*"; _name = (const char*)name; _font = XeObject::getFont(fontname); _type = XE_TEXT_ITEM; _disabled = FALSE; _cmdKey = cmdKey; _modifiers = modifiers; _label = NULL; XeMenuItem::getResources(); // Get klabel, cmdKey and modifiers from X resources: // XrmValue value; XeObject::appendResource(_name.constCharP(), "MenuTextField"); if (_menu->getResource("key", "key", &value)) { // find first occurence of ':' value.size--; char* p = (char*)memchr(value.addr, ':', value.size); if (p) { parseKeyString(value.addr, p - value.addr, _cmdKey, _modifiers); int klen = value.size - unsigned(p - value.addr) - 1; klabel = (char*)alloca(klen + 1); if (klen) { p++; klen = (klen <= XE_TEXTITEM_KEYLEN) ? klen : XE_TEXTITEM_KEYLEN; strncpy(klabel, p, klen); klabel[klen] = 0; } else *klabel = 0; } } if(label && !_label) _label = strdup(label); *_keyStr = 0; if(_cmdKey) { if(_modifiers & ControlMask) strcat(_keyStr, "Ctl+"); if(_modifiers & Mod1Mask) strcat(_keyStr, "Alt+"); if(_modifiers & ShiftMask) strcat(_keyStr, "Shift+"); //strcat(_keyStr, XKeysymToString(cmdKey)); strcat(_keyStr, klabel); _menu->_base->addKeyBinding(_menu, _cmdKey, _modifiers); } XGCValues values; ulong valmask; valmask = GCForeground | GCFont; values.foreground = BlackPixel(gDisplay, gScreenNum); values.font = _font->fid; _textGC = XeObject::getGC(_menu, &values, valmask); values.foreground = lookupColour(_menu, "grey85"); _disabledGC = XeObject::getGC(_menu, &values, valmask); // Set height and width of rect. Width to be used as hint // for width of actual menu. _rect.height = _font->ascent + _font->descent + 2 * _hiliteWidth; int cmdwidth = 0; if(*_keyStr) cmdwidth = XTextWidth(_font, _keyStr, strlen(_keyStr)) + 4 + _hiliteWidth; if(cmdwidth > _menu->_rightMargin) _menu->_rightMargin = cmdwidth; int labelwidth = 0; if (_label) labelwidth = XTextWidth(_font, _label, strlen(_label)); labelwidth += 2 + GAP + _hiliteWidth + _menu->_leftMargin; _rect.width = labelwidth + cmdwidth; } XeTextItem::~XeTextItem(void) { if(_label) free(_label); } void XeTextItem::setLabel(const char* label) { if (_label) free(_label); _label = strdup(label); int cmdwidth = 0; if (*_keyStr) cmdwidth = XTextWidth(_font, _keyStr, strlen(_keyStr)) + 4 + _hiliteWidth; int labelwidth = XTextWidth(_font, _label, strlen(_label)) + 2 + GAP + _hiliteWidth + _menu->_leftMargin; _rect.width = labelwidth + cmdwidth; _menu->refresh(); } void XeTextItem::draw(void) { int x, y; GC gc = (_disabled) ? _disabledGC : _textGC; x = _rect.x + _hiliteWidth + 2 + _menu->_leftMargin; y = _rect.y + _hiliteWidth + _font->ascent; const char* txt = _label; if(!txt) txt = _name.constCharP(); XDrawString(gDisplay, _menu->getWindow(), gc, x, y, txt, strlen(txt)); if(*_keyStr) { x = _rect.x + _menu->_widest->_rect.width - _menu->_rightMargin + 2; XDrawString(gDisplay, _menu->getWindow(), gc, x, y, _keyStr, strlen(_keyStr)); } } void XeTextItem::hilite(bool state) { XRectangle r = _rect; r.width = _menu->_widest->_rect.width; if(state) { _menu->drawBorder(r, 1, _menu->_rshadowGC, _menu->_shadowGC); XFillRectangle(gDisplay, _menu->_window, _menu->_hiliteGC, r.x+1,r.y+1, r.width-2, r.height-2); draw(); } else { XFillRectangle(gDisplay, _menu->_window, _menu->_backGC, r.x, r.y, r.width, r.height); draw(); } } XeSeparatorItem::XeSeparatorItem(XeMenu *menu) : XeMenuItem(menu) { _disabled = TRUE; _type = XE_SEPARATOR; _rect.height = 2; } XeSeparatorItem::~XeSeparatorItem(void) { } void XeSeparatorItem::draw(void) { int x1, x2, y; x1 = _menu->_borderWidth; y = _rect.y; x2 = _menu->width() - x1; XDrawLine(gDisplay, _menu->getWindow(), _menu->_shadowGC, x1, y, x2, y); y++; XDrawLine(gDisplay, _menu->getWindow(), _menu->_rshadowGC, x1, y, x2, y); } //////////////////////////////////////////////////////////////////////////////// // // Implementation of menu itself. // XeMenu::XeMenu(XeObject* parent, char* name, const char* className) : XeWidget(NULL, name, className) { // Even though the parent may not be a container (eg XeMenuBar) // the menu insinuates itself as a child of it for reasons of // resource setting. // If parent == NULL then the menu gets it's resources globally: // ie: Xe.menuName.menuItem: if (parent) { _parent = parent; _base = parent->getBase(); } // XXX - Something of a hack! since the menu may be part of // a dialog box... gObjectTable[_window] = this; _itemList.init(); _borderWidth = 2; _selected = _widest = NULL; _leftMargin = 1; _rightMargin = 0; ulong valuemask; XSetWindowAttributes attr; valuemask = CWOverrideRedirect | CWSaveUnder; attr.override_redirect = True; attr.save_under = True; XChangeWindowAttributes(gDisplay, _window, valuemask, &attr); XGCValues values; ulong valmask; valmask = GCForeground; values.foreground = lookupColour(this, "grey40"); _shadowGC = getGC(this, &values, valmask); values.foreground = lookupColour(this, "grey90"); _rshadowGC = getGC(this, &values, valmask); values.foreground = lookupColour(this, "grey85"); _hiliteGC = getGC(this, &values, valmask); values.foreground = _background; _backGC = getGC(this, &values, valmask); XSelectInput(gDisplay, _window, (ButtonReleaseMask | ButtonMotionMask | ExposureMask)); resize(_width, _borderWidth * 2); } XeMenu::~XeMenu(void) { KrLink* l = _itemList.next(); KrLink* next; while (l != &_itemList) { next = l->next(); delete (XeMenuItem*)l; l = next; } } void XeMenu::insertItem(XeMenuItem* item) { item->_rect.x = _borderWidth; if (_itemList.next() == &_itemList) { // menu is empty item->_rect.y = _borderWidth; if (item->_type != XeMenuItem::XE_SEPARATOR) item->_index = 0; } else { // menu isn't empty XeMenuItem* tail = (XeMenuItem*)_itemList.prev(); item->_rect.y = tail->_rect.y + tail->_rect.height; if (item->_type != XeMenuItem::XE_SEPARATOR) { // Search for previous item that isn't a separator: // KrLink* l = _itemList.prev(); while (l != &_itemList && ((XeMenuItem*)l)->_index == -1) l = l->prev(); if (l == &_itemList) item->_index = 0; else item->_index = ((XeMenuItem*)l)->_index + 1; } } // Insert at end of item list: // item->insertBefore(&_itemList); if(!_widest || item->_rect.width > _widest->_rect.width) { _widest = item; resize(_widest->_rect.width + 2 * _borderWidth, _height + _widest->_rect.height); } else { resize(_width, _height + item->_rect.height); } } void XeMenu::removeItem(XeMenuItem* item) { uint height = item->_rect.height; // update following items rects and indicies KrLink* l = item->next(); while (l != &_itemList) { ((XeMenuItem*)l)->_rect.y -= height; if (((XeMenuItem*)l)->_index != -1) ((XeMenuItem*)l)->_index--; l = l->next(); } // remove item item->remove(); int newWidth = _width; // refind widest item if it is the one we are removing. if (item == _widest) { _widest = NULL; KrLink* l = _itemList.next(); while (l != &_itemList) { if (!_widest || ((XeMenuItem*)l)->_rect.width > _widest->_rect.width) _widest = (XeMenuItem*)l; l = l->next(); } if (_widest) newWidth = _widest->_rect.width + 2 * _borderWidth; } resize(newWidth, _height - height); delete item; } XeMenuItem* XeMenu::getItem(int index) { KrLink* l = _itemList.next(); while (l != &_itemList) { if (((XeMenuItem*)l)->_index == index) return ((XeMenuItem*)l); l = l->next(); } return NULL; } void XeMenu::refresh(void) { // one of the menu items has changed size, // horizontally for now, find widest item and resize menu _widest = NULL; KrLink* l = _itemList.next(); while (l != &_itemList) { if (!_widest || ((XeMenuItem*)l)->_rect.width > _widest->_rect.width) _widest = (XeMenuItem*)l; l = l->next(); } resize(_widest->_rect.width + 2 * _borderWidth, _height); } void XeMenu::insertTextItem(char* name, char* label, char* klabel, KeySym cmdKey, uint modifiers) { insertItem(new XeTextItem(this, name, label, klabel, cmdKey, modifiers)); } void XeMenu::insertSeparator(void) { insertItem(new XeSeparatorItem(this)); } int XeMenu::doEvent(XEvent* theEvent) { switch(theEvent->type) { case Expose: doExpose((XExposeEvent*)theEvent); break; case ButtonRelease: break; case MotionNotify: break; } return 0; } void XeMenu::doMotion(XMotionEvent*) { } void XeMenu::doExpose(XExposeEvent*) { XRectangle r = { 0, 0, _width, _height }; drawBorder(r, _borderWidth, _rshadowGC, _shadowGC); KrLink* l = _itemList.next(); while (l != &_itemList) { ((XeMenuItem*)l)->draw(); l = l->next(); } } // // This function is called by the object processing // motion events and has coordinates local to this menu // void XeMenu::doMotion(int, int y) { KrLink* l = _itemList.next(); XeMenuItem* temp; while (l != &_itemList) { temp = (XeMenuItem*)l; if(y >= temp->_rect.y && y < temp->_rect.y + temp->_rect.height) { if(temp->_type == XeMenuItem::XE_SEPARATOR) break; if(temp == _selected) break; if(_selected) _selected->hilite(FALSE); _selected = (temp->_disabled) ? (XeMenuItem*)NULL : temp; if(!temp->_disabled) temp->hilite(TRUE); break; } l = l->next(); } } // // Called by the object processing events, when // the mouse button is released. // void XeMenu::doSelect(void) { if(_selected) { XeMenuCBData cbdata; cbdata._menu = this; cbdata._item = _selected; callCallbacks(XE_CB_ACTIVATE, &cbdata); _selected = NULL; } } // // Called when pointer leaves boundary of menu // void XeMenu::doLeaveNotify(void) { if(_selected) { _selected->hilite(FALSE); _selected = NULL; } } // // Handle command key events registered by menu items. // int XeMenu::commandKey(KeySym key, uint modifiers) { KrLink* l = _itemList.next(); XeMenuItem* temp; while (l != &_itemList) { temp = (XeMenuItem*)l; if((temp->_cmdKey == key) && temp->_modifiers == modifiers && !temp->_disabled) { XeMenuCBData cbdata; cbdata._menu = this; cbdata._item = temp; callCallbacks(XE_CB_ACTIVATE, &cbdata); break; } l = l->next(); } return 0; }