/* * Copyright (c) 1991-1993 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the Computer Systems * Engineering Group at Lawrence Berkeley Laboratory. * 4. Neither the name of the University nor of the Laboratory may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char rcsid[] = "@(#) $Header: sitebox.cc,v 1.27 96/05/16 05:21:35 van Exp $ (LBL)"; #endif #ifndef WIN32 #include #endif #include #include #include #include #if defined(sgi) #define vfork fork #endif #include "Tcl.h" #include "tkwidget.h" #include "idlecallback.h" #include class SiteBox; class Site : public TclObject { public: Site(Tk_Window, SiteBox&); ~Site(); void draw() const; virtual int command(int argc, const char*const* argv); int height(); void place(int x, int y, int w, int h); static int textwidth(const char* s); inline const char* text() const { return (text_); } inline const char* tag() const { return (tag_); } Site* next_; protected: void text(const char*); void tag(const char*); void draw_checkbox(Display*, Drawable, GC) const; void square(Display* dpy, Drawable window, GC gc, int x, int y, int side) const; GC raw_gc(Font fid, XColor* fg, XColor* bg, Drawable d) const; void free_gc(GC gc) const; char* text_; /* string appearing in display */ char* tag_; /* string appearing in display */ int textlen_; /* strlen(text_) */ int text_x_; int text_y_; int x_; int y_; int height_; int width_; int descent_; int ascent_; int minor_; int major_; #define SF_HIGHLIGHT 0x01 #define SF_DISABLE 0x02 int state_; int mute_; int rank_; Tk_Window tk_; SiteBox& sitebox_; static GC copy_gc_; static GC fg_[4]; static GC bg_[4]; static XColor* fc_; /* foreground */ static XColor* bc_; /* background */ static XColor* ac_; /* activeBackground */ static XColor* dc_; /* disabled */ static Drawable pixmap_; static int pixw_; static int pixh_; static Tk_Font fs_; /* font metrics */ }; class SiteBox : public TkWidget, public IdleCallback { public: SiteBox(const char* path); virtual int command(int argc, const char*const* argv); void setchan(int channel); void LowLightAll(); inline int Nsites() const { return (nsites_); } void idle_callback(); void change_name(); private: Site* create(); Site* remove(const char* n); void reset(); void append(Site* s); void layout(); void sort_sites(); virtual void resize(); virtual void draw(); virtual void update(); Site** sitelist_; int nsites_; int nsitelist_; int empties_; int ncol_; /* number of columns in site display */ int colitems_; /* number of items that fit for ncol_ */ int percol_; /* number of sites per column */ int itemht_; /* vertical dist allocated to a site */ int place_x_; int place_y_; GC bg_; GC fg_; int sorted_; int need_sort_; int need_layout_; int need_redraw_; }; GC Site::copy_gc_; GC Site::fg_[4]; GC Site::bg_[4]; Tk_Font Site::fs_; XColor* Site::fc_; XColor* Site::bc_; XColor* Site::ac_; XColor* Site::dc_; Drawable Site::pixmap_; int Site::pixw_; int Site::pixh_; const int vpad = 2; Site::Site(Tk_Window tk, SiteBox& sb) : next_(0), text_(0), tag_(0), textlen_(0), text_x_(0), text_y_(0), x_(0), y_(0), height_(0), width_(0), state_(0), mute_(0), rank_(4), tk_(tk), sitebox_(sb) { Tcl& tcl = Tcl::instance(); Tk_FontMetrics fm; if (fs_ == 0) { const char* font = tcl.attr("siteFont"); fs_ = Tk_GetFont(tcl.interp(), tk_, (char*)font); if (fs_ == 0) { fprintf(stderr, "vat: couldn't find font: %s\n", font); fs_ = Tk_GetFont(tcl.interp(), tk_, "screen"); if (fs_ == 0) fs_ = Tk_GetFont(tcl.interp(), tk_, "fixed"); if (fs_ == 0) { fprintf(stderr, "vat: couldn't find screen or fixed font\n"); exit(1); } } if (sitebox_.mono()) { fc_ = sitebox_.getcolor("black", "black"); bc_ = sitebox_.getcolor("white", "white"); } else { fc_ = sitebox_.getcolor(tcl.attr("foreground"), "black"); bc_ = sitebox_.getcolor(tcl.attr("background"), "white"); } ac_ = sitebox_.getcolor(tcl.attr("highlightColor"), "white"); dc_ = sitebox_.getcolor(tcl.attr("disabledColor"), "gray"); copy_gc_ = sitebox_.lookup_gc(0, 0, 0); } Tk_GetFontMetrics(fs_, &fm); descent_ = fm.descent; ascent_ = fm.ascent; major_ = ascent_; minor_ = major_ / 2; } Site::~Site() { if (text_) delete text_; if (tag_) delete tag_; } int Site::command(int argc, const char*const* argv) { Tcl& tcl = Tcl::instance(); if (argc == 2) { if (strcmp(argv[1], "text") == 0) { tcl.result(text_); return (TCL_OK); } if (strcmp(argv[1], "mute") == 0) { tcl.result(mute_ ? "1" : "0"); return (TCL_OK); } if (strcmp(argv[1], "rank") == 0) { sprintf(tcl.buffer(), "%d", rank_); tcl.result(tcl.buffer()); return (TCL_OK); } } else if (argc == 3) { if (strcmp(argv[1], "highlight") == 0) { if (atoi(argv[2])) state_ |= SF_HIGHLIGHT; else state_ &=~ SF_HIGHLIGHT; draw(); return (TCL_OK); } if (strcmp(argv[1], "disable") == 0) { if (atoi(argv[2])) state_ |= SF_DISABLE; else state_ &=~ SF_DISABLE; draw(); return (TCL_OK); } if (strcmp(argv[1], "tag") == 0) { tag(argv[2]); return (TCL_OK); } if (strcmp(argv[1], "text") == 0) { text(argv[2]); sitebox_.change_name(); return (TCL_OK); } if (strcmp(argv[1], "mute") == 0) { mute_ = atoi(argv[2]); draw(); return (TCL_OK); } if (strcmp(argv[1], "rank") == 0) { rank_ = atoi(argv[2]); draw(); return (TCL_OK); } } return (TclObject::command(argc, argv)); } /* * Must be called after first site created. */ inline int Site::textwidth(const char* s) { return (Tk_TextWidth(fs_, s, strlen(s))); } inline void Site::square(Display* dpy, Drawable window, GC gc, int x, int y, int side) const { int r = side >> 1; side = (r << 1) + 1; XFillRectangle(dpy, window, gc, x - r, y - r, side, side); } void Site::draw_checkbox(Display* dpy, Drawable window, GC gc) const { int cy = (height_ - 1) / 2; int cx = 2 + cy; #ifdef notdef cx += x_; cy += y_; #endif /* * Draw the rank indicator. */ if (rank_ < 3) { int side = minor_; switch (rank_) { case 0: square(dpy, window, gc, cx, cy, side); break; case 2: side -= 2; /* fall through */ case 1: square(dpy, window, (state_ >= 2) ? gc : fg_[state_ + 2], cx, cy, side); break; } } /* * Draw the mute button. */ const int r = major_ >> 1; const int s = r << 1; XDrawRectangle(dpy, window, gc, cx - r, cy - r, s, s); if (mute_) { /* * Draw an `X' through the mute button. */ XDrawLine(dpy, window, gc, cx - r, cy + r, cx + r, cy - r); XDrawLine(dpy, window, gc, cx - r, cy - r, cx + r, cy + r); } } void Site::draw() const { if (width_ == 0) return; Display* dpy = Tk_Display(tk_); Drawable d = pixmap_; if (d == 0) return; GC gc = fg_[state_]; XFillRectangle(dpy, d, bg_[state_], 0, 0, width_, height_); draw_checkbox(dpy, d, gc); XDrawString(dpy, d, gc, text_x_, text_y_, text_, textlen_); XDrawLine(dpy, d, fg_[0], 0, height_ - 1, width_ - 1, height_ - 1); XCopyArea(dpy, d, Tk_WindowId(tk_), copy_gc_, 0, 0, width_, height_, x_, y_); } void Site::text(const char* newtext) { delete text_; textlen_ = strlen(newtext); text_ = new char[textlen_ + 1]; strcpy(text_, newtext); draw(); } void Site::tag(const char* s) { delete tag_; tag_ = new char[strlen(s) + 1]; strcpy(tag_, s); } int Site::height() { /* * Make sure height allocation is even. So less one * pixel for bottom border is odd, which gives us a * center line. */ int h = ascent_ + descent_ + 2 * vpad; h += h & 1; return (h); } /* * XXX Allocate a gc without going through tk. We cannot use * TkWidget::lookup_gc because it allocates the gc using the * on-screen window and we need to allocate it using the pixmap * to workaround a bug in the Parallax X server. */ GC Site::raw_gc(Font fid, XColor* fg, XColor* bg, Drawable d) const { XGCValues v; u_long mask; sitebox_.set_gcv(v, mask, fid, fg, bg); #ifdef PARALLAX_BUG return (XCreateGC(Tk_Display(tk_), d, mask, &v)); #else return (Tk_GetGC(tk_, mask, &v)); #endif } void Site::free_gc(GC gc) const { #ifdef PARALLAX_BUG XFreeGC(Tk_Display(tk_), gc); #else Tk_FreeGC(Tk_Display(tk_), gc); #endif } void Site::place(int x, int y, int w, int h) { width_ = w; height_ = h; x_ = x; y_ = y; text_x_ = height_ + 4; text_y_ = height_ - (descent_ + vpad + 1); if ((pixw_ < w || pixh_ < h) && w > 0 && h > 0) { pixw_ = w; pixh_ = h; Display* dpy = Tk_Display(tk_); if (pixmap_ != 0) XFreePixmap(dpy, pixmap_); pixmap_ = XCreatePixmap(dpy, Tk_WindowId(tk_), w, h, Tk_Depth(tk_)); /* * Reallocate GCs on this pixmap to work around a bug * in the Parallax X server. */ if (fg_[0] != 0) { free_gc(fg_[0]); free_gc(fg_[1]); free_gc(fg_[2]); free_gc(fg_[3]); free_gc(bg_[0]); free_gc(bg_[1]); } Font fid = Tk_FontId(fs_); fg_[0] = raw_gc(fid, fc_, bc_, pixmap_); fg_[2] = raw_gc(fid, dc_, bc_, pixmap_); bg_[0] = raw_gc(fid, bc_, bc_, pixmap_); if (sitebox_.mono()) { fg_[1] = raw_gc(fid, bc_, fc_, pixmap_); fg_[3] = raw_gc(fid, dc_, fc_, pixmap_); bg_[1] = raw_gc(fid, fc_, fc_, pixmap_); } else { fg_[1] = raw_gc(fid, fc_, ac_, pixmap_); fg_[3] = raw_gc(fid, dc_, ac_, pixmap_); bg_[1] = raw_gc(fid, ac_, ac_, pixmap_); } bg_[2] = bg_[0]; bg_[3] = bg_[1]; } } void SiteBox::change_name() { if (sorted_) { need_sort_ = 1; idle_sched(); } } void SiteBox::resize() { reset(); layout(); } void SiteBox::layout() { need_layout_ = 0; int n = nsites_; if (n <= 0) { place_x_ = 0; place_y_ = 0; return; } percol_ = height_ / itemht_; if (percol_ <= 0) percol_ = 1; ncol_ = (n + percol_ - 1) / percol_; if (ncol_ <= 0) ncol_ = 1; colitems_ = ncol_ * percol_; int w = width_ / ncol_; int j = 0; int x = -w; int y = 0; int nc = ncol_; for (int i = 0; i < n; i++) { if (--j <= 0) { j = percol_; y = 0; x += w; if (--nc <= 0) /* * Absorb round off error in * last column */ w = width_ - (ncol_ - 1) * w; } Site* s = sitelist_[i]; if (s != 0) s->place(x, y, w, itemht_); y += itemht_; } place_x_ = x; place_y_ = y; } void SiteBox::reset() { ncol_ = 1; colitems_ = 0; place_x_ = 0; place_y_ = 0; } /* * Place a new site in the next slot. * If we run out of room, do a resize * to force a new layout computation. */ void SiteBox::append(Site* s) { int n = nsites_; if (n > colitems_) { if (n == 1) itemht_ = s->height(); need_layout_ = 1; } else { /* * Place this site below the previous one. */ int w = width_ / ncol_; w = width_ - (ncol_ - 1) * w; s->place(place_x_, place_y_, w, itemht_); place_y_ += itemht_; } need_redraw_ = 1; if (sorted_) need_sort_ = 1; idle_sched(); } void SiteBox::update() { draw(); } /* * This is a heavyweight redraw. It gets called only on exposures. * Highlighting etc. is generally done directly by the Site objects. */ void SiteBox::draw() { need_redraw_ = 0; int n = nsites_; if (n <= 0 || width_ == 0) return; /* * All sites get redrawn below and they fill in their * whole allocation. So to avoid screen flicker, * only erase the blank areas. */ if (ncol_ > 1) { XFillRectangle(Tk_Display(tk_), Tk_WindowId(tk_), bg_, place_x_ + 1, place_y_, width_ - place_x_, height_ - place_y_); int h = height_ - percol_ * itemht_; if (h > 0) XFillRectangle(Tk_Display(tk_), Tk_WindowId(tk_), bg_, 0, height_ - h, width_, h); } else { XFillRectangle(Tk_Display(tk_), Tk_WindowId(tk_), bg_, 0, place_y_, width_, height_ - place_y_); } Display* const dpy = Tk_Display(tk_); const Drawable window = Tk_WindowId(tk_); for (int i = 0; i < n; i++) { Site* s = sitelist_[i]; if (s != 0) s->draw(); } if (ncol_ > 1 && colitems_ > n) { int x = (ncol_ - 1) * (width_ / ncol_); int h = (colitems_ - n) * itemht_; int bot = percol_ * itemht_; XDrawLine(dpy, window, fg_, x, bot - h, x, bot); } } static class SiteBoxMatcher : public Matcher { public: SiteBoxMatcher() : Matcher("sitebox") {} TclObject* match(const char* path) { return (new SiteBox(path)); } } sitebox_matcher; SiteBox::SiteBox(const char* path) : TkWidget(path, "Vat", 0, 0, 0) { nsitelist_ = 256; sitelist_ = (Site**)malloc(nsitelist_ * sizeof(Site*)); memset((char*)sitelist_, 0, nsitelist_ * sizeof(Site*)); nsites_ = 0; empties_ = 0; ncol_ = 1; colitems_ = 0; itemht_ = 1; percol_ = 1; sorted_ = 1; need_sort_ = 0; Tcl& tcl = Tcl::instance(); Tk_Uid fg = mono()? (char*)"black" : (char*)tcl.attr("foreground"); Tk_Uid bg = mono()? (char*)"white" : (char*)tcl.attr("background"); fg_ = lookup_gc(0, fg, bg); bg_ = lookup_gc(0, bg, bg); } Site* SiteBox::create() { Site* s = new Site(tk_, *this); sitelist_[nsites_] = s; /* make sure table is null terminated */ if (++nsites_ >= nsitelist_) { int n = nsitelist_ * 2; sitelist_ = (Site**)realloc(sitelist_, n * sizeof(*sitelist_)); memset((char*)&sitelist_[nsitelist_], 0, nsitelist_ * sizeof(*sitelist_)); nsitelist_ = n; } return (s); } Site* SiteBox::remove(const char* n) { Site* s; for (Site **sp = sitelist_; (s = *sp) != 0; ++sp) { if (strcmp(s->name(), n) == 0) { --nsites_; Site* target = s; for (;;) { s = sp[1]; if ((*sp++ = s) == 0) return (target); } } } return (0); } static int compareSite(const void* p1, const void* p2) { Site* s1 = *(Site**)p1; Site* s2 = *(Site**)p2; const char* n1 = s1->text(); while (*n1 && isascii(*n1) && !isalpha(*n1)) ++n1; const char* n2 = s2->text(); while (*n2 && isascii(*n2) && !isalpha(*n2)) ++n2; return (strcasecmp(n1, n2)); } void SiteBox::sort_sites() { register int n = nsites_; Site** sp = sitelist_; qsort(sp, n, sizeof(*sp), (int(*)(const void*, const void*))compareSite); need_sort_ = 0; } void SiteBox::idle_callback() { if (need_sort_) { sort_sites(); need_layout_ = 1; } if (need_layout_) { reset(); layout(); need_redraw_ = 1; } if (need_redraw_) draw(); } int SiteBox::command(int argc, const char*const* argv) { Tcl& tcl = Tcl::instance(); register int i; if (argc == 2) { if (strcmp(argv[1], "create") == 0) { Site* s = create(); append(s); tcl.result(s->name()); return (TCL_OK); } if (strcmp(argv[1], "sites") == 0) { sprintf(tcl.buffer(), "%d", nsites_); tcl.result(tcl.buffer()); return (TCL_OK); } if (strcmp(argv[1], "sort") == 0) { need_sort_ = 1; idle_sched(); return (TCL_OK); } } else if (argc == 3) { if (strcmp(argv[1], "remove") == 0) { Site* s = remove(argv[2]); delete s; need_layout_ = 1; idle_sched(); return (TCL_OK); } if (strcmp(argv[1], "keep-sorted") == 0) { sorted_ = atoi(argv[2]); if (sorted_) { need_sort_ = 1; idle_sched(); } return (TCL_OK); } } else if (argc == 4) { if (strcmp(argv[1], "which") == 0) { int x, y, h, v, dw; x = atoi(argv[2]); y = atoi(argv[3]); v = y / itemht_; dw = width_ / ncol_; h = x / dw; i = h * percol_ + v; if (i >= 0 && i < nsites_) { const char* s = sitelist_[i]->tag(); if (s != 0) tcl.result(s); } return (TCL_OK); } if (strcmp(argv[1], "over-button") == 0) { int x, y, h, v, dw, cb = 0; x = atoi(argv[2]); y = atoi(argv[3]); v = y / itemht_; dw = width_ / ncol_; h = x / dw; i = h * percol_ + v; if (0 <= i && i < nsites_ && x - h * dw < itemht_) cb = 1; sprintf(tcl.buffer(), "%d", cb); tcl.result(tcl.buffer()); return (TCL_OK); } } return (TclObject::command(argc, argv)); }