/* * Smithsonian Astrophysical Observatory, Cambridge, MA, USA * This code has been modified under the terms listed below and is made * available under the same terms. */ /* * Copyright 1993-2004 George A Howlett. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #ifdef USE_TK_STUBS #include #endif #include "bltGrBind.h" #include "bltGraph.h" #include "bltGrLegd.h" #include "bltGrElem.h" #include "bltGrPostscript.h" #include "bltGrMisc.h" #include "bltGrDef.h" #include "bltConfig.h" #include "bltGrPSOutput.h" using namespace Blt; static void SelectCmdProc(ClientData); static Tk_SelectionProc SelectionProc; // OptionSpecs static const char* selectmodeObjOption[] = { "single", "multiple", NULL }; static const char* positionObjOption[] = { "rightmargin", "leftmargin", "topmargin", "bottommargin", "plotarea", "xy", NULL }; static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_BORDER, "-activebackground", "activeBackground", "ActiveBackground", STD_ACTIVE_BACKGROUND, -1, Tk_Offset(LegendOptions, activeBg), 0, NULL, CACHE}, {TK_OPTION_PIXELS, "-activeborderwidth", "activeBorderWidth", "ActiveBorderWidth", STD_BORDERWIDTH, -1, Tk_Offset(LegendOptions, entryBW), 0, NULL, LAYOUT}, {TK_OPTION_COLOR, "-activeforeground", "activeForeground", "ActiveForeground", STD_ACTIVE_FOREGROUND, -1, Tk_Offset(LegendOptions, activeFgColor), 0, NULL, CACHE}, {TK_OPTION_RELIEF, "-activerelief", "activeRelief", "ActiveRelief", "flat", -1, Tk_Offset(LegendOptions, activeRelief), 0, NULL, LAYOUT}, {TK_OPTION_ANCHOR, "-anchor", "anchor", "Anchor", "n", -1, Tk_Offset(LegendOptions, anchor), 0, NULL, LAYOUT}, {TK_OPTION_SYNONYM, "-bg", NULL, NULL, NULL, -1, 0, 0, "-background", 0}, {TK_OPTION_BORDER, "-background", "background", "Background", NULL, -1, Tk_Offset(LegendOptions, normalBg), TK_OPTION_NULL_OK, NULL, CACHE}, {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", STD_BORDERWIDTH, -1, Tk_Offset(LegendOptions, borderWidth), 0, NULL, LAYOUT}, {TK_OPTION_SYNONYM, "-bd", NULL, NULL, NULL, -1, 0, 0, "-borderwidth", 0}, {TK_OPTION_INT, "-columns", "columns", "columns", "0", -1, Tk_Offset(LegendOptions, reqColumns), 0, NULL, LAYOUT}, {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", "ExportSelection", "no", -1, Tk_Offset(LegendOptions, exportSelection), 0, NULL, LAYOUT}, {TK_OPTION_CUSTOM, "-focusdashes", "focusDashes", "FocusDashes", "dot", -1, Tk_Offset(LegendOptions, focusDashes), TK_OPTION_NULL_OK, &dashesObjOption, CACHE}, {TK_OPTION_COLOR, "-focusforeground", "focusForeground", "FocusForeground", STD_ACTIVE_FOREGROUND, -1, Tk_Offset(LegendOptions, focusColor), 0, NULL, CACHE}, {TK_OPTION_FONT, "-font", "font", "Font", STD_FONT_SMALL, -1, Tk_Offset(LegendOptions, style.font), 0, NULL, LAYOUT}, {TK_OPTION_SYNONYM, "-fg", NULL, NULL, NULL, -1, 0, 0, "-foreground", 0}, {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", STD_NORMAL_FOREGROUND, -1, Tk_Offset(LegendOptions, fgColor), 0, NULL, CACHE}, {TK_OPTION_BOOLEAN, "-hide", "hide", "Hide", "no", -1, Tk_Offset(LegendOptions, hide), 0, NULL, LAYOUT}, {TK_OPTION_PIXELS, "-ipadx", "iPadX", "Pad", "1", -1, Tk_Offset(LegendOptions, ixPad), 0, NULL, LAYOUT}, {TK_OPTION_PIXELS, "-ipady", "iPadY", "Pad", "1", -1, Tk_Offset(LegendOptions, iyPad), 0, NULL, LAYOUT}, {TK_OPTION_BORDER, "-nofocusselectbackground", "noFocusSelectBackground", "NoFocusSelectBackground", STD_ACTIVE_BACKGROUND, -1, Tk_Offset(LegendOptions, selOutFocusBg), 0, NULL, CACHE}, {TK_OPTION_COLOR, "-nofocusselectforeground", "noFocusSelectForeground", "NoFocusSelectForeground", STD_ACTIVE_FOREGROUND, -1, Tk_Offset(LegendOptions, selOutFocusFgColor), 0, NULL, CACHE}, {TK_OPTION_PIXELS, "-padx", "padX", "Pad", "1", -1, Tk_Offset(LegendOptions, xPad), 0, NULL, LAYOUT}, {TK_OPTION_PIXELS, "-pady", "padY", "Pad", "1", -1, Tk_Offset(LegendOptions, yPad), 0, NULL, LAYOUT}, {TK_OPTION_STRING_TABLE, "-position", "position", "Position", "rightmargin", -1, Tk_Offset(LegendOptions, position), 0, &positionObjOption, LAYOUT}, {TK_OPTION_BOOLEAN, "-raised", "raised", "Raised", "no", -1, Tk_Offset(LegendOptions, raised), 0, NULL, LAYOUT}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", "flat", -1, Tk_Offset(LegendOptions, relief), 0, NULL, LAYOUT}, {TK_OPTION_INT, "-rows", "rows", "rows", "0", -1, Tk_Offset(LegendOptions, reqRows), 0, NULL, LAYOUT}, {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "SelectBackground", STD_ACTIVE_BACKGROUND, -1, Tk_Offset(LegendOptions, selInFocusBg), 0, NULL, LAYOUT}, {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth", "SelectBorderWidth", "1", -1, Tk_Offset(LegendOptions, selBW), 0, NULL, LAYOUT}, {TK_OPTION_STRING, "-selectcommand", "selectCommand", "SelectCommand", NULL, -1, Tk_Offset(LegendOptions, selectCmd), TK_OPTION_NULL_OK, NULL, 0}, {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "SelectForeground", STD_ACTIVE_FOREGROUND, -1, Tk_Offset(LegendOptions, selInFocusFgColor), 0, NULL, CACHE}, {TK_OPTION_STRING_TABLE, "-selectmode", "selectMode", "SelectMode", "multiple", -1, Tk_Offset(LegendOptions, selectMode), 0, &selectmodeObjOption, 0}, {TK_OPTION_RELIEF, "-selectrelief", "selectRelief", "SelectRelief", "flat", -1, Tk_Offset(LegendOptions, selRelief), 0, NULL, LAYOUT}, {TK_OPTION_STRING, "-title", "title", "Title", NULL, -1, Tk_Offset(LegendOptions, title), TK_OPTION_NULL_OK, NULL, LAYOUT}, {TK_OPTION_COLOR, "-titlecolor", "titleColor", "TitleColor", STD_NORMAL_FOREGROUND, -1, Tk_Offset(LegendOptions, titleStyle.color), 0, NULL, CACHE}, {TK_OPTION_FONT, "-titlefont", "titleFont", "TitleFont", STD_FONT_SMALL, -1, Tk_Offset(LegendOptions, titleStyle.font), 0, NULL, LAYOUT}, {TK_OPTION_PIXELS, "-x", "x", "X", "0", -1, Tk_Offset(LegendOptions, xReq), 0, NULL, LAYOUT}, {TK_OPTION_PIXELS, "-y", "y", "Y", "0", -1, Tk_Offset(LegendOptions, yReq), 0, NULL, LAYOUT}, {TK_OPTION_END, NULL, NULL, NULL, NULL, -1, 0, 0, NULL, 0} }; Legend::Legend(Graph* graphPtr) { ops_ = (void*)calloc(1, sizeof(LegendOptions)); LegendOptions* ops = (LegendOptions*)ops_; graphPtr_ = graphPtr; flags =0; nEntries_ =0; nColumns_ =0; nRows_ =0; width_ =0; height_ =0; entryWidth_ =0; entryHeight_ =0; x_ =0; y_ =0; bindTable_ =NULL; focusGC_ =NULL; focusPtr_ =NULL; selAnchorPtr_ =NULL; selMarkPtr_ =NULL; selected_ = Blt_Chain_Create(); titleWidth_ =0; titleHeight_ =0; ops->style.anchor =TK_ANCHOR_NW; ops->style.color =NULL; ops->style.font =NULL; ops->style.angle =0; ops->style.justify =TK_JUSTIFY_LEFT; ops->titleStyle.anchor =TK_ANCHOR_NW; ops->titleStyle.color =NULL; ops->titleStyle.font =NULL; ops->titleStyle.angle =0; ops->titleStyle.justify =TK_JUSTIFY_LEFT; bindTable_ = new BindTable(graphPtr, this); Tcl_InitHashTable(&selectTable_, TCL_ONE_WORD_KEYS); Tk_CreateSelHandler(graphPtr_->tkwin_, XA_PRIMARY, XA_STRING, SelectionProc, this, XA_STRING); optionTable_ =Tk_CreateOptionTable(graphPtr->interp_, optionSpecs); Tk_InitOptions(graphPtr->interp_, (char*)ops_, optionTable_, graphPtr->tkwin_); } Legend::~Legend() { // LegendOptions* ops = (LegendOptions*)ops_; delete bindTable_; if (focusGC_) graphPtr_->freePrivateGC(focusGC_); if (graphPtr_->tkwin_) Tk_DeleteSelHandler(graphPtr_->tkwin_, XA_PRIMARY, XA_STRING); Blt_Chain_Destroy(selected_); Tk_FreeConfigOptions((char*)ops_, optionTable_, graphPtr_->tkwin_); free(ops_); } int Legend::configure() { LegendOptions* ops = (LegendOptions*)ops_; // GC for active label, Dashed outline unsigned long gcMask = GCForeground | GCLineStyle; XGCValues gcValues; gcValues.foreground = ops->focusColor->pixel; gcValues.line_style = (LineIsDashed(ops->focusDashes)) ? LineOnOffDash : LineSolid; GC newGC = graphPtr_->getPrivateGC(gcMask, &gcValues); if (LineIsDashed(ops->focusDashes)) { ops->focusDashes.offset = 2; graphPtr_->setDashes(newGC, &ops->focusDashes); } if (focusGC_) graphPtr_->freePrivateGC(focusGC_); focusGC_ = newGC; return TCL_OK; } void Legend::map(int plotWidth, int plotHeight) { LegendOptions* ops = (LegendOptions*)ops_; entryWidth_ =0; entryHeight_ = 0; nRows_ =0; nColumns_ =0; nEntries_ =0; height_ =0; width_ = 0; TextStyle tts(graphPtr_, &ops->titleStyle); tts.getExtents(ops->title, &titleWidth_, &titleHeight_); // Count the number of legend entries and determine the widest and tallest // label. The number of entries would normally be the number of elements, // but elements can have no legend entry (-label ""). int nEntries =0; int maxWidth =0; int maxHeight =0; TextStyle ts(graphPtr_, &ops->style); for (Blt_ChainLink link=Blt_Chain_FirstLink(graphPtr_->elements_.displayList); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; int w, h; ts.getExtents(elemOps->label, &w, &h); if (maxWidth < (int)w) maxWidth = w; if (maxHeight < (int)h) maxHeight = h; nEntries++; } if (nEntries == 0) return; Tk_FontMetrics fontMetrics; Tk_GetFontMetrics(ops->style.font, &fontMetrics); int symbolWidth = 2 * fontMetrics.ascent; maxWidth += 2 * ops->entryBW + 2*ops->ixPad + + symbolWidth + 3 * 2; maxHeight += 2 * ops->entryBW + 2*ops->iyPad; maxWidth |= 0x01; maxHeight |= 0x01; int lw = plotWidth - 2 * ops->borderWidth - 2*ops->xPad; int lh = plotHeight - 2 * ops->borderWidth - 2*ops->yPad; /* * The number of rows and columns is computed as one of the following: * * both options set User defined. * -rows Compute columns from rows. * -columns Compute rows from columns. * neither set Compute rows and columns from * size of plot. */ int nRows =0; int nColumns =0; if (ops->reqRows > 0) { nRows = MIN(ops->reqRows, nEntries); if (ops->reqColumns > 0) nColumns = MIN(ops->reqColumns, nEntries); else nColumns = ((nEntries - 1) / nRows) + 1; /* Only -rows. */ } else if (ops->reqColumns > 0) { /* Only -columns. */ nColumns = MIN(ops->reqColumns, nEntries); nRows = ((nEntries - 1) / nColumns) + 1; } else { // Compute # of rows and columns from the legend size nRows = lh / maxHeight; nColumns = lw / maxWidth; if (nRows < 1) { nRows = nEntries; } if (nColumns < 1) { nColumns = nEntries; } if (nRows > nEntries) { nRows = nEntries; } switch ((Position)ops->position) { case TOP: case BOTTOM: nRows = ((nEntries - 1) / nColumns) + 1; break; case LEFT: case RIGHT: default: nColumns = ((nEntries - 1) / nRows) + 1; break; } } if (nColumns < 1) nColumns = 1; if (nRows < 1) nRows = 1; lh = (nRows * maxHeight); if (titleHeight_ > 0) lh += titleHeight_ + ops->yPad; lw = nColumns * maxWidth; if (lw < (int)(titleWidth_)) lw = titleWidth_; width_ = lw + 2 * ops->borderWidth + 2*ops->xPad; height_ = lh + 2 * ops->borderWidth + 2*ops->yPad; nRows_ = nRows; nColumns_ = nColumns; nEntries_ = nEntries; entryHeight_ = maxHeight; entryWidth_ = maxWidth; int row =0; int col =0; int count =0; for (Blt_ChainLink link=Blt_Chain_FirstLink(graphPtr_->elements_.displayList); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); count++; elemPtr->row_ = row; elemPtr->col_ = col; row++; if ((count % nRows) == 0) { col++; row = 0; } } } void Legend::draw(Drawable drawable) { LegendOptions* ops = (LegendOptions*)ops_; GraphOptions* gops = (GraphOptions*)graphPtr_->ops_; if ((ops->hide) || (nEntries_ == 0)) return; setOrigin(); Tk_Window tkwin = graphPtr_->tkwin_; int w = width_; int h = height_; Pixmap pixmap = Tk_GetPixmap(graphPtr_->display_, Tk_WindowId(tkwin), w, h, Tk_Depth(tkwin)); if (ops->normalBg) Tk_Fill3DRectangle(tkwin, pixmap, ops->normalBg, 0, 0, w, h, 0, TK_RELIEF_FLAT); else { switch ((Position)ops->position) { case TOP: case BOTTOM: case RIGHT: case LEFT: Tk_Fill3DRectangle(tkwin, pixmap, gops->normalBg, 0, 0, w, h, 0, TK_RELIEF_FLAT); break; case PLOT: case XY: // Legend background is transparent and is positioned over the the // plot area. Either copy the part of the background from the backing // store pixmap or (if no backing store exists) just fill it with the // background color of the plot. if (graphPtr_->cache_ != None) XCopyArea(graphPtr_->display_, graphPtr_->cache_, pixmap, graphPtr_->drawGC_, x_, y_, w, h, 0, 0); else Tk_Fill3DRectangle(tkwin, pixmap, gops->plotBg, 0, 0, w, h, TK_RELIEF_FLAT, 0); break; }; } Tk_FontMetrics fontMetrics; Tk_GetFontMetrics(ops->style.font, &fontMetrics); int symbolSize = fontMetrics.ascent; int xMid = symbolSize + 1 + ops->entryBW; int yMid = (symbolSize / 2) + 1 + ops->entryBW; int xLabel = 2 * symbolSize + ops->entryBW + ops->ixPad + 2 * 2; int ySymbol = yMid + ops->iyPad; int xSymbol = xMid + 2; int x = ops->xPad + ops->borderWidth; int y = ops->yPad + ops->borderWidth; TextStyle tts(graphPtr_, &ops->titleStyle); tts.drawText(pixmap, ops->title, x, y); if (titleHeight_ > 0) y += titleHeight_ + ops->yPad; int count = 0; int yStart = y; TextStyle ts(graphPtr_, &ops->style); for (Blt_ChainLink link=Blt_Chain_FirstLink(graphPtr_->elements_.displayList); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; int isSelected = entryIsSelected(elemPtr); if (elemPtr->labelActive_) Tk_Fill3DRectangle(tkwin, pixmap, ops->activeBg, x, y, entryWidth_, entryHeight_, ops->entryBW, ops->activeRelief); else if (isSelected) { XColor* fg = (flags & FOCUS) ? ops->selInFocusFgColor : ops->selOutFocusFgColor; Tk_3DBorder bg = (flags & FOCUS) ? ops->selInFocusBg : ops->selOutFocusBg; ops->style.color = fg; Tk_Fill3DRectangle(tkwin, pixmap, bg, x, y, entryWidth_, entryHeight_, ops->selBW, ops->selRelief); } else { ops->style.color = ops->fgColor; if (elemOps->legendRelief != TK_RELIEF_FLAT) Tk_Fill3DRectangle(tkwin, pixmap, gops->normalBg, x, y, entryWidth_, entryHeight_, ops->entryBW, elemOps->legendRelief); } elemPtr->drawSymbol(pixmap, x + xSymbol, y + ySymbol, symbolSize); ts.drawText(pixmap, elemOps->label, x+xLabel, y+ops->entryBW+ops->iyPad); count++; if (focusPtr_ == elemPtr) { if (isSelected) { XColor* color = (flags & FOCUS) ? ops->selInFocusFgColor : ops->selOutFocusFgColor; XSetForeground(graphPtr_->display_, focusGC_, color->pixel); } XDrawRectangle(graphPtr_->display_, pixmap, focusGC_, x + 1, y + 1, entryWidth_ - 3, entryHeight_ - 3); if (isSelected) XSetForeground(graphPtr_->display_, focusGC_, ops->focusColor->pixel); } // Check when to move to the next column if ((count % nRows_) > 0) y += entryHeight_; else { x += entryWidth_; y = yStart; } } Tk_3DBorder bg = ops->normalBg; if (!bg) bg = gops->normalBg; Tk_Draw3DRectangle(tkwin, pixmap, bg, 0, 0, w, h, ops->borderWidth, ops->relief); XCopyArea(graphPtr_->display_, pixmap, drawable, graphPtr_->drawGC_, 0, 0, w, h, x_, y_); Tk_FreePixmap(graphPtr_->display_, pixmap); } void Legend::print(PSOutput* psPtr) { LegendOptions* ops = (LegendOptions*)ops_; GraphOptions* gops = (GraphOptions*)graphPtr_->ops_; PostscriptOptions* pops = (PostscriptOptions*)graphPtr_->postscript_->ops_; if ((ops->hide) || (nEntries_ == 0)) return; setOrigin(); double x = x_; double y = y_; int width = width_ - 2*ops->xPad; int height = height_ - 2*ops->yPad; psPtr->append("% Legend\n"); if (pops->decorations) { if (ops->normalBg) psPtr->fill3DRectangle(ops->normalBg, x, y, width, height, ops->borderWidth, ops->relief); else psPtr->print3DRectangle(gops->normalBg, x, y, width, height, ops->borderWidth, ops->relief); } else { psPtr->setClearBackground(); psPtr->fillRectangle(x, y, width, height); } Tk_FontMetrics fontMetrics; Tk_GetFontMetrics(ops->style.font, &fontMetrics); int symbolSize = fontMetrics.ascent; int xMid = symbolSize + 1 + ops->entryBW; int yMid = (symbolSize / 2) + 1 + ops->entryBW; int xLabel = 2 * symbolSize + ops->entryBW + ops->ixPad + 5; int xSymbol = xMid + ops->ixPad; int ySymbol = yMid + ops->iyPad; x += ops->borderWidth; y += ops->borderWidth; TextStyle tts(graphPtr_, &ops->titleStyle); tts.printText(psPtr, ops->title, x, y); if (titleHeight_ > 0) y += titleHeight_ + ops->yPad; int count = 0; double yStart = y; TextStyle ts(graphPtr_, &ops->style); for (Blt_ChainLink link=Blt_Chain_FirstLink(graphPtr_->elements_.displayList); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; if (elemPtr->labelActive_) { ops->style.color = ops->activeFgColor; psPtr->fill3DRectangle(ops->activeBg, x, y, entryWidth_, entryHeight_, ops->entryBW, ops->activeRelief); } else { ops->style.color = ops->fgColor; if (elemOps->legendRelief != TK_RELIEF_FLAT) psPtr->print3DRectangle(gops->normalBg, x, y, entryWidth_, entryHeight_, ops->entryBW, elemOps->legendRelief); } elemPtr->printSymbol(psPtr, x + xSymbol, y + ySymbol, symbolSize); ts.printText(psPtr, elemOps->label, x + xLabel, y + ops->entryBW + ops->iyPad); count++; if ((count % nRows_) > 0) y += entryHeight_; else { x += entryWidth_; y = yStart; } } } void Legend::removeElement(Element* elemPtr) { bindTable_->deleteBindings(elemPtr); } void Legend::eventuallyInvokeSelectCmd() { if ((flags & SELECT_PENDING) == 0) { flags |= SELECT_PENDING; Tcl_DoWhenIdle(SelectCmdProc, this); } } void Legend::setOrigin() { LegendOptions* ops = (LegendOptions*)ops_; GraphOptions* gops = (GraphOptions*)graphPtr_->ops_; int x =0; int y =0; int w =0; int h =0; switch ((Position)ops->position) { case RIGHT: w = gops->rightMargin.width - gops->rightMargin.axesOffset; h = graphPtr_->bottom_ - graphPtr_->top_; x = graphPtr_->right_ + gops->rightMargin.axesOffset; y = graphPtr_->top_; break; case LEFT: w = gops->leftMargin.width - gops->leftMargin.axesOffset; h = graphPtr_->bottom_ - graphPtr_->top_; x = graphPtr_->inset_; y = graphPtr_->top_; break; case TOP: w = graphPtr_->right_ - graphPtr_->left_; h = gops->topMargin.height - gops->topMargin.axesOffset; if (gops->title) h -= graphPtr_->titleHeight_; x = graphPtr_->left_; y = graphPtr_->inset_; if (gops->title) y += graphPtr_->titleHeight_; break; case BOTTOM: w = graphPtr_->right_ - graphPtr_->left_; h = gops->bottomMargin.height - gops->bottomMargin.axesOffset; x = graphPtr_->left_; y = graphPtr_->bottom_ + gops->bottomMargin.axesOffset; break; case PLOT: w = graphPtr_->right_ - graphPtr_->left_; h = graphPtr_->bottom_ - graphPtr_->top_; x = graphPtr_->left_; y = graphPtr_->top_; break; case XY: w = width_; h = height_; x = ops->xReq; y = ops->yReq; if (x < 0) x += graphPtr_->width_; if (y < 0) y += graphPtr_->height_; break; } switch (ops->anchor) { case TK_ANCHOR_NW: break; case TK_ANCHOR_W: if (h > height_) y += (h - height_) / 2; break; case TK_ANCHOR_SW: if (h > height_) y += (h - height_); break; case TK_ANCHOR_N: if (w > width_) x += (w - width_) / 2; break; case TK_ANCHOR_CENTER: if (h > height_) y += (h - height_) / 2; if (w > width_) x += (w - width_) / 2; break; case TK_ANCHOR_S: if (w > width_) x += (w - width_) / 2; if (h > height_) y += (h - height_); break; case TK_ANCHOR_NE: if (w > width_) x += w - width_; break; case TK_ANCHOR_E: if (w > width_) x += w - width_; if (h > height_) y += (h - height_) / 2; break; case TK_ANCHOR_SE: if (w > width_) { x += w - width_; } if (h > height_) { y += (h - height_); } break; } x_ = x + ops->xPad; y_ = y + ops->yPad; } void Legend::selectEntry(Element* elemPtr) { switch (flags & SELECT_TOGGLE) { case SELECT_CLEAR: deselectElement(elemPtr); break; case SELECT_SET: selectElement(elemPtr); break; case SELECT_TOGGLE: Tcl_HashEntry* hPtr = Tcl_FindHashEntry(&selectTable_, (char*)elemPtr); if (hPtr) deselectElement(elemPtr); else selectElement(elemPtr); break; } } void Legend::selectElement(Element* elemPtr) { int isNew; Tcl_HashEntry* hPtr = Tcl_CreateHashEntry(&selectTable_, elemPtr, &isNew); if (isNew) { Blt_ChainLink link = Blt_Chain_Append(selected_, elemPtr); Tcl_SetHashValue(hPtr, link); } } void Legend::deselectElement(Element* elemPtr) { Tcl_HashEntry* hPtr = Tcl_FindHashEntry(&selectTable_, elemPtr); if (hPtr) { Blt_ChainLink link = (Blt_ChainLink)Tcl_GetHashValue(hPtr); Blt_Chain_DeleteLink(selected_, link); Tcl_DeleteHashEntry(hPtr); } } int Legend::selectRange(Element *fromPtr, Element *toPtr) { if (Blt_Chain_IsBefore(fromPtr->link, toPtr->link)) { for (Blt_ChainLink link=fromPtr->link; link; link=Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); selectEntry(elemPtr); if (link == toPtr->link) break; } } else { for (Blt_ChainLink link=fromPtr->link; link; link=Blt_Chain_PrevLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); selectEntry(elemPtr); if (link == toPtr->link) break; } } return TCL_OK; } void Legend::clearSelection() { LegendOptions* ops = (LegendOptions*)ops_; Tcl_DeleteHashTable(&selectTable_); Tcl_InitHashTable(&selectTable_, TCL_ONE_WORD_KEYS); Blt_Chain_Reset(selected_); if (ops->selectCmd) eventuallyInvokeSelectCmd(); } int Legend::entryIsSelected(Element* elemPtr) { Tcl_HashEntry* hPtr = Tcl_FindHashEntry(&selectTable_, (char*)elemPtr); return (hPtr != NULL); } int Legend::getElementFromObj(Tcl_Obj* objPtr, Element** elemPtrPtr) { const char *string = Tcl_GetString(objPtr); Element* elemPtr = NULL; if (!strcmp(string, "anchor")) elemPtr = selAnchorPtr_; else if (!strcmp(string, "current")) elemPtr = (Element*)bindTable_->currentItem(); else if (!strcmp(string, "first")) elemPtr = getFirstElement(); else if (!strcmp(string, "focus")) elemPtr = focusPtr_; else if (!strcmp(string, "last")) elemPtr = getLastElement(); else if (!strcmp(string, "end")) elemPtr = getLastElement(); else if (!strcmp(string, "next.row")) elemPtr = getNextRow(focusPtr_); else if (!strcmp(string, "next.column")) elemPtr = getNextColumn(focusPtr_); else if (!strcmp(string, "previous.row")) elemPtr = getPreviousRow(focusPtr_); else if (!strcmp(string, "previous.column")) elemPtr = getPreviousColumn(focusPtr_); else if (string[0] == '@') { int x, y; if (graphPtr_->getXY(string, &x, &y) != TCL_OK) return TCL_ERROR; ClassId classId; elemPtr = (Element*)pickEntry(x, y, &classId); } else { if (graphPtr_->getElement(objPtr, &elemPtr) != TCL_OK) return TCL_ERROR; if (!elemPtr->link) { Tcl_AppendResult(graphPtr_->interp_, "bad legend index \"", string, "\"", (char *)NULL); return TCL_ERROR; } ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) elemPtr = NULL; } *elemPtrPtr = elemPtr; return TCL_OK; } Element* Legend::getNextRow(Element* focusPtr) { int col = focusPtr->col_; int row = focusPtr->row_ + 1; for (Blt_ChainLink link=focusPtr->link; link; link=Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; if ((elemPtr->col_ == col) && (elemPtr->row_ == row)) return elemPtr; } return NULL; } Element* Legend::getNextColumn(Element* focusPtr) { int col = focusPtr->col_ + 1; int row = focusPtr->row_; for (Blt_ChainLink link=focusPtr->link; link; link=Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; if ((elemPtr->col_ == col) && (elemPtr->row_ == row)) return elemPtr; } return NULL; } Element* Legend::getPreviousRow(Element* focusPtr) { int col = focusPtr->col_; int row = focusPtr->row_ - 1; for (Blt_ChainLink link=focusPtr->link; link; link=Blt_Chain_PrevLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; if ((elemPtr->col_ == col) && (elemPtr->row_ == row)) return elemPtr; } return NULL; } Element* Legend::getPreviousColumn(Element* focusPtr) { int col = focusPtr->col_ - 1; int row = focusPtr->row_; for (Blt_ChainLink link=focusPtr->link; link; link=Blt_Chain_PrevLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (!elemOps->label) continue; if ((elemPtr->col_ == col) && (elemPtr->row_ == row)) return elemPtr; } return NULL; } Element* Legend::getFirstElement() { for (Blt_ChainLink link=Blt_Chain_FirstLink(graphPtr_->elements_.displayList); link; link=Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (elemOps->label) return elemPtr; } return NULL; } Element* Legend::getLastElement() { for (Blt_ChainLink link=Blt_Chain_LastLink(graphPtr_->elements_.displayList); link; link=Blt_Chain_PrevLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (elemOps->label) return elemPtr; } return NULL; } ClientData Legend::pickEntry(int xx, int yy, ClassId* classIdPtr) { LegendOptions* ops = (LegendOptions*)ops_; int ww = width_; int hh = height_; if (titleHeight_ > 0) yy -= titleHeight_ + ops->yPad; xx -= x_ + ops->borderWidth; yy -= y_ + ops->borderWidth; ww -= 2 * ops->borderWidth + 2*ops->xPad; hh -= 2 * ops->borderWidth + 2*ops->yPad; // In the bounding box? if so, compute the index if (xx >= 0 && xx < ww && yy >= 0 && yy < hh) { int row = yy / entryHeight_; int column = xx / entryWidth_; int nn = (column * nRows_) + row; // Legend entries are stored in bottom-to-top if (nn < nEntries_) { int count = 0; for (Blt_ChainLink link = Blt_Chain_FirstLink(graphPtr_->elements_.displayList); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); ElementOptions* elemOps = (ElementOptions*)elemPtr->ops(); if (elemOps->label) { if (count == nn) { *classIdPtr = elemPtr->classId(); return elemPtr; } count++; } } } } return NULL; } // Support static int SelectionProc(ClientData clientData, int offset, char *buffer, int maxBytes) { Legend* legendPtr = (Legend*)clientData; Graph* graphPtr = legendPtr->graphPtr_; LegendOptions* ops = (LegendOptions*)legendPtr->ops(); if ((ops->exportSelection) == 0) return -1; // Retrieve the names of the selected entries Tcl_DString dString; Tcl_DStringInit(&dString); if (legendPtr->flags & SELECT_SORTED) { for (Blt_ChainLink link=Blt_Chain_FirstLink(legendPtr->selected_); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); Tcl_DStringAppend(&dString, elemPtr->name_, -1); Tcl_DStringAppend(&dString, "\n", -1); } } else { for (Blt_ChainLink link=Blt_Chain_FirstLink(graphPtr->elements_.displayList); link; link = Blt_Chain_NextLink(link)) { Element* elemPtr = (Element*)Blt_Chain_GetValue(link); if (legendPtr->entryIsSelected(elemPtr)) { Tcl_DStringAppend(&dString, elemPtr->name_, -1); Tcl_DStringAppend(&dString, "\n", -1); } } } int nBytes = Tcl_DStringLength(&dString) - offset; strncpy(buffer, Tcl_DStringValue(&dString) + offset, maxBytes); Tcl_DStringFree(&dString); buffer[maxBytes] = '\0'; return MIN(nBytes, maxBytes); } static void SelectCmdProc(ClientData clientData) { Legend* legendPtr = (Legend*)clientData; LegendOptions* ops = (LegendOptions*)legendPtr->ops(); Tcl_Preserve(legendPtr); legendPtr->flags &= ~SELECT_PENDING; if (ops->selectCmd) { Tcl_Interp* interp = legendPtr->graphPtr_->interp_; if (Tcl_GlobalEval(interp, ops->selectCmd) != TCL_OK) Tcl_BackgroundError(interp); } Tcl_Release(legendPtr); }