Logo Search packages:      
Sourcecode: qps version File versions

htable.C

// htable.C
//
// This program is free software. See the file COPYING for details.
// Author: Mattias Engdegård, 1997-1999

// TODO:
// * autoscroll speed proportional to distance from edge
// * interface to add/remove rows (for disclosure triangles)
// * interface to display pixmaps in cells (for disclosure triangles etc)
// * include sorting functionality here for more generality

#include <stdlib.h>

#include <qaccel.h>
#include <qbitmap.h>
#include <qpainter.h>
#include "htable.h"
#include "svec.C"

#if QT_VERSION >= 200 && !defined(QTNAME)
#define QTNAME(x) Qt::x
#else
#define QTNAME(x) x
#endif

// HeadingTip: tooltips for headings

HeadingTip::HeadingTip(QWidget *parent)
          : QToolTip(parent)
{}

void HeadingTip::maybeTip(const QPoint &pos)
{
    ((TableHead *)parentWidget())->tip(pos);
}

// TableHead: the horizontally scrollable table head

TableHead::TableHead(HeadedTable *parent)
          : QtTableView(parent),
          htable(parent),
          titlefont(parent->font()),
          fm(titlefont),
          left_pressed(FALSE),
          dragging(FALSE)
{
    setBackgroundColor(QColor(0xc0c0c0)); // standard Qt colour
    setTableFlags(Tbl_smoothHScrolling | Tbl_scrollLastHCell);
    setFont(titlefont);
    cellheight = (int)(fm.lineSpacing() * 1.3);
    setCellHeight(cellheight);
    setNumRows(1);
    if(htable->options & HTBL_HEADING_TOOLTIPS)
      htip = new HeadingTip(this);
}

void drawMacPanel(QPainter *p, int x, int y, int w, int h,
              bool sunken)
{
    QColor nw1, se1, c1;
    QColor nw2, se2, c2;

    if(sunken) {
      nw1 = QColor(0x111111);
      se1 = QColor(0x444444);
      c1 = QColor(0x222222);
      nw2 = QColor(0x555555);
      se2 = QColor(0xaaaaaa);
      c2 = QColor(0x777777);
      p->fillRect(x + 2, y + 2, w - 4, h - 4, QColor(0x888888));
    } else {
      nw1 = QColor(0x606060); // re-use some Qt colours
      se1 = QColor(0x333333);
      c1 = QColor(0x555555);
      nw2 = QColor(0xffffff);
      se2 = QColor(0x888888);
      c2 = QColor(0xc0c0c0);  // Qt colour
    }
    p->setPen(nw1);
    p->drawLine(x, y, x, y + h - 2);
    p->drawLine(x, y, x + w - 2, y);
    p->setPen(c1);
    p->drawPoint(x, y + h - 1);
    p->drawPoint(x + w - 1, y);
    p->setPen(se1);
    p->drawLine(x + 1, y + h - 1, x + w - 1, y + h - 1);
    p->drawLine(x + w - 1, y + 1, x + w - 1, y + h - 1);
    p->setPen(nw2);
    p->drawLine(x + 1, y + 1, x + 1, y + h - 3);
    p->drawLine(x + 1, y + 1, x + w - 3, y + 1);
    p->setPen(c2);
    p->drawPoint(x + 1, y + h - 2);
    p->drawPoint(x + w - 2, y + 1);
    p->setPen(se2);
    p->drawLine(x + 2, y + h - 2, x + w - 2, y + h - 2);
    p->drawLine(x + w - 2, y + 2, x + w - 2, y + h - 2);
}

void TableHead::paintCell(QPainter *p, int, int col)
{
    int lcol = htable->logcols[col];
    int gap = htable->leftGap(lcol);
    int w = cellWidth(col);
    drawMacPanel(p, 0, 0, cellWidth(col), cellheight,
             lcol == htable->sorted_col);
    p->setPen(QTNAME(black));
    int a = htable->alignment(lcol);
    if(a == AlignRight)
      w -= 4;                 // don't go too close to the right border
    p->drawText(gap, 0, w - gap, height(),
            AlignVCenter | a,
            htable->title(lcol));
}

int TableHead::cellWidth(int col)
{
    return htable->actualColWidth(htable->logcols[col]);
}

void TableHead::scrollSideways(int val)
{
    setXOffset(val);
}

// draw a physical column heading in the "sunken" state
void TableHead::drawSunken(int col)
{
    int oldsorted = htable->sorted_col;
    htable->sorted_col = htable->logcols[col];
    updateCell(0, col, FALSE);
    htable->sorted_col = oldsorted;
}

// paint a (unsunken) heading of physical column at offset x
void TableHead::paintHeading(int x, int col)
{
    int w = cellWidth(col);
    int h = height();
    QPainter p(this);
    p.setClipRect(x, 0, w, h);
    p.translate(x, 0);
    p.eraseRect(0, 0, w, h);
    int oldsorted = htable->sorted_col;
    htable->sorted_col = -1;
    paintCell(&p, 0, col);
    htable->sorted_col = oldsorted;
}

void TableHead::mousePressEvent(QMouseEvent *e)
{
    int col = findCol(e->x());
    if(col == -1) return;
    if(e->button() == RightButton
       && htable->options & HTBL_HEADING_CONTEXT_MENU) {
      htable->emit_right_click_heading_signal(mapToGlobal(e->pos()),
                                    htable->logcols[col]);
    } else if(e->button() == LeftButton
            && htable->options & HTBL_HEADING_CLICK) {
      left_pressed = TRUE;
      press = e->pos();
      drawSunken(col);
      click_col = col;
    }
}

void TableHead::mouseMoveEvent(QMouseEvent *e)
{
    if(left_pressed) {
      if(htable->options & HTBL_REORDER_COLS
         && (dragging || abs(e->x() - press.x()) > drag_threshold)) {
          if(click_col < 0)
            click_col = findCol(press.x());
          int w = cellWidth(click_col);
          if(!dragging) {
            dragging = TRUE;
            updateCell(0, click_col);
            drag_offset = press.x() - htable->colOffset(click_col)
                          + xOffset();
          } else {
            htable->body->drawGhostCol(drag_pos - drag_offset, w);
            repaint(drag_pos - drag_offset, 0, w, height());
          }
          paintHeading(e->x() - drag_offset, click_col);
          htable->body->drawGhostCol(e->x() - drag_offset, w);
          drag_pos = e->x();
          return;
      }
      int col = findCol(e->x());
      if(col == findCol(press.x())) {
          if(click_col < 0) {
            drawSunken(col);
            click_col = col;
          }
      } else {
          if(click_col >= 0) {
            updateCell(0, click_col);
            click_col = -1;
          }
      }
    }
}

void TableHead::mouseReleaseEvent(QMouseEvent *e)
{
    if(left_pressed) {
      left_pressed = FALSE;
      if(dragging) {
          int w = cellWidth(click_col);
          repaint(drag_pos - drag_offset, 0, w, height());
          htable->body->drawGhostCol(drag_pos - drag_offset, w);
          dragging = FALSE;
          int col = findCol(e->x());
          if(col < 0)
            col = (e->x() < 0) ? 0 : htable->ncols - 1;
          htable->moveCol(htable->logcols[click_col], col);
          return;
      }
      int col = findCol(e->x());
      if(col == findCol(press.x())) {
          htable->emit_click_signal(htable->logcols[col]);
      } else if(click_col >= 0)
          updateCell(0, click_col);
    }
}

void TableHead::tip(const QPoint &pos)
{
    int col = findCol(pos.x());
    if(col != -1) {
      QString s = htable->tipText(htable->logcols[col]);
      if(!s.isEmpty()) {
          int x;
          colXPos(col, &x);
          htip->tip(QRect(x, 0, cellWidth(col), height()), s);
      }
    }
}

// TableBody: the table body, scrollable in all ways

TableBody::TableBody(HeadedTable *parent)
          : QtTableView(parent),
          htable(parent),
          fm(parent->font())
{
    setBackgroundColor(background = QColor(0xeeeeee));
    setTableFlags(Tbl_autoScrollBars | Tbl_smoothScrolling
              | Tbl_clipCellPainting);
    setFont(parent->font());
    ((QScrollBar *)horizontalScrollBar())->setTracking(TRUE);

    first_drag_row = prev_drag_row = -1;
    autoscrolling = FALSE;
    gadget_click = FALSE;
}

void TableBody::setFont(const QFont &f)
{
    QtTableView::setFont(f);
    fm = f;
    cellheight = fm.lineSpacing();
    setCellHeight(cellheight);
    zerowidth = fm.width('0');
}

void TableBody::paintCell(QPainter *p, int row, int col)
{
    int w = cellWidth(col);
    p->setPen(QTNAME(white));
    p->drawLine(0, cellheight - 1, w, cellheight - 1);
    p->setPen(colorGroup().text());
    bool sort = htable->logcols[col] == htable->sortedCol();
    if(htable->isSelected(row)) {
      if(sort)
          p->setBackgroundColor(htable->sel_bg.light(100 * 0xdd / 0xee));
      else
          p->setBackgroundColor(htable->sel_bg);
      p->setPen(htable->sel_fg);
      int h = cellheight - 1;
      p->eraseRect(0, 0, w, h);
    } else {
      p->setPen(QTNAME(black));
      if(sort) {
          p->setBackgroundColor(0xdddddd);
          p->eraseRect(0, 0, w, cellheight - 1);
      }
    }
    htable->drawCellContents(row, col, w, cellheight, p);
    p->setBackgroundColor(background);
}

void TableBody::scrollUp()
{
    setYOffset(yOffset() - cellheight);
}

void TableBody::scrollDown()
{
    setYOffset(yOffset() + cellheight);
}

void TableBody::pageUp()
{
    setYOffset(yOffset() - viewHeight());
}

void TableBody::pageDown()
{
    setYOffset(yOffset() + viewHeight());
}

void TableBody::scrollLeft()
{     
    setXOffset(xOffset() - cellheight);
}

void TableBody::scrollRight()
{
    // Qt bug: setXOffset() doesn't clamp to maximum offset
    setXOffset(QMIN(xOffset() + cellheight, maxXOffset()));
}

void TableBody::jumpTop()
{
    setYOffset(0);
}

void TableBody::jumpBottom()
{
    setYOffset(maxYOffset());
}

void TableBody::centerVertically(int row)
{
    int topcell = row - viewHeight() / (cellheight * 2);
    setTopCell(QMAX(topcell, 0));
}

void TableBody::showRange(int from, int to)
{
    int h = viewHeight() / cellheight;
    if(to >= topCell() + h)
      setTopCell(QMAX(0, QMIN(from, to - h + 1)));
}

int TableBody::cellWidth(int col)
{
    return htable->actualColWidth(htable->logcols[col]);
}

void TableBody::updateRow(int row)
{
    for(int col = 0; col < htable->ncols; col++)
      updateCell(row, col);
}

void TableBody::mousePressEvent(QMouseEvent *e)
{
    int row = findRow(e->y());
    if((htable->options & HTBL_ROW_SELECTION) && e->button() == LeftButton) {
      if(row == -1) {
          htable->clearAllSelections();
          if(e->y() >= 0)
            row = numRows();
          first_drag_row = prev_drag_row = row;
      } else {
          if(e->state() & ControlButton) {
            int col = findCol(e->x());
            if(col >= 0) {
                int lcol = htable->logcols[col];
                QString s = htable->cachedText(row, lcol);
                for(int i = 0; i < numRows(); i++) {
                  if(s == htable->cachedText(i, lcol))
                      htable->setSelected(i, TRUE);
                  else if(!(e->state() & ShiftButton))
                      htable->setSelected(i, FALSE);
                }
            }
          } else {
            if(htable->treemode && htable->folding
               && e->x() < htable->gadget_space
                           + htable->treestep * htable->rowDepth(row)
               && htable->folded(row) != HeadedTable::Leaf) {
                htable->emit_fold(row);
                gadget_click = TRUE;
                return;
            }
            if(e->state() & ShiftButton)
                htable->setSelected(row, !htable->isSelected(row));
            else
                htable->selectOnly(row);
            first_drag_row = prev_drag_row = row;
          }
      }
      htable->selectionNotify();

    } else if((htable->options & HTBL_ROW_CONTEXT_MENU)
            && e->button() == RightButton && row != -1) {
      if((htable->options & HTBL_ROW_SELECTION) && !htable->isSelected(row))
          htable->selectOnly(row);
      htable->selectionNotify();
      htable->emit_right_click_signal(mapToGlobal(e->pos()));
    }
}

void TableBody::mouseDoubleClickEvent(QMouseEvent *e)
{
    if(htable->options & HTBL_ROW_DOUBLE_CLICK && e->button() == LeftButton) {
      int row = findRow(e->y());
      if(row != -1) {
          if(htable->options & HTBL_ROW_SELECTION
             && !htable->isSelected(row))
            htable->selectOnly(row);
          htable->selectionNotify();
          htable->emit_double_click_signal(row);
      }
    }
}

void TableBody::mouseMoveEvent(QMouseEvent *e)
{
    if(!(htable->options & HTBL_ROW_SELECTION)
       || e->state() & ControlButton || gadget_click)
      return;
    int row = findRow(e->y());
    if(row != prev_drag_row) {
      if(row == -1) {
          if(!autoscrolling) {
            // dragging outside table, cause scrolling
            scrolldir = (e->y() < 0) ? UP : DOWN;
            killTimers();
            startTimer(scroll_delay);
            autoscrolling = TRUE;
          }
      } else {
          killTimers();
          autoscrolling = FALSE;
          dragSelectTo(row);
      }
    }
}

void TableBody::mouseReleaseEvent(QMouseEvent *)
{
    gadget_click = FALSE;
    if(autoscrolling) {
      killTimers();           // no more autoscrolling
      first_drag_row = prev_drag_row = -1;
      autoscrolling = FALSE;
    }
}

void TableBody::timerEvent(QTimerEvent *)
{
    if(!autoscrolling) return;
    killTimers();
    if(scrolldir == UP) {
      int top = topCell();
      setTopCell((top > 1) ? top - 1 : 0);
      dragSelectTo(topCell());
    } else {
      setTopCell(topCell() + 1);
      int bottom = lastRowVisible();
      dragSelectTo((bottom < numRows()) ? bottom : numRows() - 1);
    }
    startTimer(scroll_delay);
}

// change drag selection point from previous drag position to row
void TableBody::dragSelectTo(int row)
{
    int dir = (row > prev_drag_row) ? 1 : -1;
    if((prev_drag_row - first_drag_row) * dir >= 0) {
      // moving away from start point
      for(int i = prev_drag_row + dir; i - dir != row; i += dir)
          htable->setSelected(i, TRUE);
    } else {
      // moving towards start point
      for(int i = prev_drag_row; i != row; i += dir)
          htable->setSelected(i, FALSE);
    }
    prev_drag_row = row;
    htable->selectionNotify();
}

// heuristic for determining a good XOR color: This is in general a hard
// problem but since we know (most of) the background and the foreground,
// we can try. Note that this function might not return an allocated QColor,
// so it is only useful for XOR drawing.

QColor TableBody::getXorColor()
{
    QColor fg(QTNAME(black));
    return QColor(0, fg.pixel() ^ backgroundColor().pixel());
}

void TableBody::drawGhostCol(int x, int w)
{
    static QColor xorcol = getXorColor();

    QPainter p(this);
    p.setPen(xorcol);
    p.setRasterOp(XorROP);
    p.drawLine(x, 0, x, height());
    p.drawLine(x + w, 0, x + w, height());
}

// HeadedTable: the actually useable class

HeadedTable::HeadedTable(QWidget *parent, int opts)
            : QWidget(parent),
            options(opts),
            logcols(8), physcols(8),
            text_cache(17)
{
    QFont f = font();
    f.setBold(FALSE);
    setFont(f);
    head = new TableHead(this);
    body = new TableBody(this);
    sorted_col = -1;
    nrows = ncols = -1;
    nselected = 0;
    text_cache.setAutoDelete(TRUE);
    treemode = FALSE;
    folding = FALSE;
    lines = FALSE;
    treestep = body->cellheight;
    gadget_space = 0;

    // connect keyboard shortcuts
    QAccel *acc = new QAccel(this);
    acc->connectItem(acc->insertItem(Key_Up),
                 body, SLOT(scrollUp()));
    acc->connectItem(acc->insertItem(Key_Down),
                 body, SLOT(scrollDown()));
    acc->connectItem(acc->insertItem(Key_Left),
                 body, SLOT(scrollLeft()));
    acc->connectItem(acc->insertItem(Key_Right),
                 body, SLOT(scrollRight()));
    acc->connectItem(acc->insertItem(Key_Prior),
                 body, SLOT(pageUp()));
    acc->connectItem(acc->insertItem(Key_Next),
                 body, SLOT(pageDown()));
    acc->connectItem(acc->insertItem(Key_Home),
                 body, SLOT(jumpTop()));
    acc->connectItem(acc->insertItem(Key_End),
                 body, SLOT(jumpBottom()));

    // synchronize horizontal scrolling of head and body
    connect(body->horizontalScrollBar(), SIGNAL(valueChanged(int)),
          head, SLOT(scrollSideways(int)));
}

HeadedTable::~HeadedTable()
{
}

void HeadedTable::invalidateCache()
{
    text_cache.clear();
}

// give cached text of given row/logical col
QString HeadedTable::cachedText(int row, int col)
{
    unsigned k = (row << 10) + col; // assuming max 1K cols, 4M rows
    QString *s = text_cache[k];
    if(!s) {
      s = new QString(text(row, col));
      text_cache.insert(k, s);
      if(text_cache.count() > text_cache.size() * 3)
          text_cache.resize(text_cache.count());
    }
    return *s;
}

void HeadedTable::setNumRows(int rows)
{
    selected.fill(FALSE, rows);
    nselected = 0;
    nrows = rows;
    body->setNumRows(rows);
}

void HeadedTable::setNumCols(int cols)
{
    if(cols != ncols) {
      ncols = cols;

      // reset permutation tables to the identity mapping
      logcols.setSize(cols);
      physcols.setSize(cols);
      for(int i = 0; i < cols; i++)
          logcols[i] = physcols[i] = i;

      resetWidths();
      head->setNumCols(cols);
      body->setNumCols(cols);
    }
}

// Remove an element from a permutation of (0, 1, ... n-1),
// yielding a permutation of (0, 1, ... n-2)
static void del_element(Svec<int> *v, int elem)
{
    for(int i = 0, j = 0; i < v->size(); i++) {
      int vi = (*v)[i];
      if(vi != elem)
          (*v)[j++] = (vi > elem) ? vi - 1 : vi;
    }
    v->setSize(v->size() - 1);
}

// Add an element to a permutation of (0, 1, ... n-1),
// yielding a permutation of (0, 1, ... n). place is where elem is to be
// inserted.
static void ins_element(Svec<int> *v, int elem, int place)
{
    v->setSize(v->size() + 1);
    for(int i = v->size() - 2, j = i + 1; i >= 0; i--) {
      if(j == place)
          (*v)[j--] = elem;
      int iv = (*v)[i];
      if(iv >= elem)
          ++iv;
      (*v)[j--] = iv;
    }
    if(place == 0)
      (*v)[0] = elem;
}

static void invert_permutation(Svec<int> *src, Svec<int> *dst)
{
    for(int i = 0; i < src->size(); i++)
      (*dst)[(*src)[i]] = i;
}

// Move logical column to new physical place
void HeadedTable::moveCol(int col, int place)
{
    int oldplace = physcols[col];
    if(place == oldplace)
      return;
    if(treemode && (place == 0 || oldplace == 0)) {
      resetWidth(col);
      resetWidth(logcols[place]);
    }
    if(place > oldplace) {
      for(int i = oldplace; i < place; i++)
          logcols[i] = logcols[i + 1];
    } else {
      for(int i = oldplace; i > place; i--)
          logcols[i] = logcols[i - 1];
    }
    logcols[place] = col;

    invert_permutation(&logcols, &physcols);
    updateColWidths();
    if(treemode && (place == 0 || oldplace == 0))
      repaintAll();
    else
      repaintColumns(QMIN(place, oldplace), QMAX(place, oldplace));
    emit colMoved(col, place);
}

void HeadedTable::setPhysCols(Svec<int> *phys)
{
    physcols = *phys;
    ncols = physcols.size();
    logcols.setSize(ncols);
    invert_permutation(&physcols, &logcols);
    resetWidths();
    head->setNumCols(ncols);
    body->setNumCols(ncols);
}

// distance (in table coords) from left table edge of physical column
int HeadedTable::colOffset(int col)
{
    int x = 0;
    for(int c = 0; c < col; c++)
      x += body->cellWidth(c);
    return x;
}


// repaint (physical) columns from col0 to col1. If col1 is -1, repaint all
// the way to the right edge of the table.
void HeadedTable::repaintColumns(int col0, int col1)
{
    QRect bvr = body->viewRect();
    QRect hvr = head->viewRect();
    int x0 = colOffset(col0) - body->xOffset();
    if(x0 > hvr.width())
      return;
    if(x0 < 0)
      x0 = 0;
    bvr.setLeft(x0);
    hvr.setLeft(x0);
    if(col1 >= 0) {
      int x1 = colOffset(col1) + body->cellWidth(col1) - body->xOffset();
      if(x1 < hvr.width()) {
          hvr.setRight(x1);
          bvr.setRight(x1);
      }
    }
    body->repaint(bvr);
    head->repaint(hvr);
}

void HeadedTable::setTreeMode(bool tm)
{
    if(tm != treemode) {
      treemode = tm;
      if(ncols >= 1)
          resetWidth(logcols[0]);
    }
}

static void paint_triangle(QPixmap *pm, int c, bool closed)
{
    int s = c / 3;
    pm->resize(s * 2 + 1, s * 2 + 1);
    pm->fill(QTNAME(white));
    QPointArray a(3);
    QPainter p(pm);
    p.setBrush(QTNAME(white));
    if(closed) {
      a[0] = QPoint(s - 2, 0);
      a[1] = QPoint(s * 2 - 2, s);
      a[2] = QPoint(s - 2, s * 2);
    } else {
      a[0] = QPoint(0, s - 2);
      a[1] = QPoint(s * 2, s - 2);
      a[2] = QPoint(s, s * 2 - 2);
    }
    p.drawPolygon(a);
    p.setPen(QColor(0x606060));
    if(closed) {
      p.drawLine(s * 2 - 2, s + 1, s - 1, s * 2);
    } else {
      p.drawLine(s * 2, s - 1, s + 1, s * 2 - 2);
    }
    pm->setMask(pm->createHeuristicMask());
}

void HeadedTable::make_gadgets()
{
    int c = body->cellheight;
    paint_triangle(&closed_pm, c, TRUE);
    paint_triangle(&open_pm, c, FALSE);
}

void HeadedTable::enableFolding(bool enable)
{
    if(folding != enable) {
      folding = enable;
      int c = body->cellheight;
      gadget_space = folding ? c + (c >> 1) : 0;
      if(treemode) {
          if(ncols >= 1)
            resetWidth(logcols[0]);
          topAndRepaint();
      }
    }
}

void HeadedTable::enableLines(bool enable)
{
    if(lines != enable) {
      lines = enable;
      if(treemode) {
          // repaint physical col 0
          QRect r = body->viewRect();
          r.setLeft(body->xOffset());
          r.setRight(body->cellWidth(0) - body->xOffset());
          body->repaint(r);
      }
    }
}

// update number of cols by deltacols, change at physical column place
void HeadedTable::updateCols(int deltacols, int place, bool update)
{
    ncols += deltacols;
    head->setAutoUpdate(FALSE);
    head->setNumCols(ncols);
    head->setAutoUpdate(TRUE);
    body->setAutoUpdate(FALSE);
    body->setNumCols(ncols);
    body->setAutoUpdate(TRUE);
    if(update)
      repaintColumns(place);
}

// delete (logical) column
void HeadedTable::deleteCol(int col, bool update)
{
    int phys = physcols[col];
    del_element(&logcols, col);
    del_element(&physcols, phys);
    widths.remove(col);
    if(col == sorted_col)
      sorted_col = -1;
    else if(col < sorted_col && sorted_col >= 0)
      sorted_col--;

    // We could walk through the cache and modify existing entries, but this
    // is easier right now
    invalidateCache();
    updateCols(-1, phys, update);
}

// Add column at a given physical place (it will be last in logical order)
void HeadedTable::addCol(int place, bool update)
{
    int log = ncols;
    ins_element(&logcols, log, place);
    ins_element(&physcols, place, log);
    widths.add(-1);

    // we needn't invalidate the cache since it is in logical columns

    updateCols(+1, place, update);
}

// set sorted (logical) column
void HeadedTable::setSortedCol(int col)
{
    if(col != sorted_col) {
      int old_sorted = sorted_col;
      sorted_col = col;
      if(old_sorted != -1 && old_sorted < ncols)
          updateHeading(old_sorted);
      if(col != -1 && col < ncols)
          updateHeading(col);
    }
}

void HeadedTable::setSelected(int row, bool sel, bool update)
{
    if(isSelected(row) != sel) {
      selected.setBit(row, sel);
      if(sel)
          nselected++;
      else
          nselected--;
      if(update) {
          body->updateRow(row);
          affected_rows.add(row);
      }
    }
}

void HeadedTable::selectionNotify()
{
    if(!affected_rows.size())
      return;
    emit selectionChanged(&affected_rows);
    affected_rows.clear();
}

void HeadedTable::clearAllSelections()
{
    for(int row = 0; row < nrows; row++)
      setSelected(row, FALSE);
}

void HeadedTable::selectOnly(int row)
{
    for(int r = 0; r < nrows; r++)
      setSelected(r, r == row);
}

void HeadedTable::resizeEvent(QResizeEvent *)
{
    head->setGeometry(0, 0, width(), head->cellheight);
    int ybody = head->height();
    body->setGeometry(0, ybody, width(), height() - ybody);
}

void HeadedTable::emit_click_signal(int col)
{
    emit titleClicked(col);
}

void HeadedTable::emit_double_click_signal(int row)
{
    emit doubleClicked(row);
}

void HeadedTable::emit_right_click_signal(QPoint where)
{
    emit rightClickedRow(where);
}

void HeadedTable::emit_right_click_heading_signal(QPoint where, int col)
{
    emit rightClickedHeading(where, col);
}

void HeadedTable::emit_fold(int row)
{
    emit foldSubTree(row);
}

// default implementation returns a null string (no tip displayed)
QString HeadedTable::tipText(int)
{
    return "";
}

// draw body cell: default implementation just draws text in the cell
// This is called after cell background has been painted
void HeadedTable::drawCellContents(int row, int col,
                           int w, int h, QPainter *p)
{
    int lcol = logcols[col];
    int gap;
    if(treemode && col == 0) {
      int d = rowDepth(row);
      int c = body->cellheight;
      if(lines) {
          p->setPen(0x888888); // less visually obtrusive than black
          int dx = folding ? gadget_space : 6;
          for(int i = d, prow = row; i > 0 && prow >= 0;
            i--, prow = parentRow(prow)) {
            int x = dx - c + i * treestep;
            if(!lastChild(prow))
                p->drawLine(x, 0, x, c - 1);
            else if(i == d)
                p->drawLine(x, 0, x, c / 2);
          }
          if(d > 0) {
            int x = dx - c + d * treestep;
            p->drawLine(x, c / 2, x + c / 2, c / 2);
          }
          p->setPen(QTNAME(black));
      }

      if(folding) {
          NodeState fs = folded(row);
          if(fs != Leaf) {
            if(closed_pm.isNull())
                make_gadgets();
            QPixmap *pm = (fs == Closed) ? &closed_pm : &open_pm;
            p->drawPixmap(c / 4 + d * treestep, (c - pm->height()) / 2,
                        *pm);
          }
      }
      gap =  gadget_space + d * treestep;
    } else
      gap = leftGap(lcol);

    p->drawText(gap, 0, w - gap, h,
            AlignVCenter | alignment(lcol),
            cachedText(row, lcol));
}

// default implementation for treeless tables
int HeadedTable::rowDepth(int)
{
    return 0;
}

// default implementation for treeless tables
HeadedTable::NodeState HeadedTable::folded(int)
{
    return Leaf;
}

// default implementation for treeless tables
int HeadedTable::parentRow(int)
{
    return 0;
}

// default implementation for treeless tables
bool HeadedTable::lastChild(int)
{
    return FALSE;
}

// updates the table size of the table (size and presence of scrollbars)
void HeadedTable::updateTableSize()
{
    if(body->xOffset() > body->maxXOffset())
      body->setXOffset(body->maxXOffset());

    head->updateTableSize();
    body->updateTableSize();
    updateColWidths();
}

// Update the QtTableView's notion of horizontal offset in case column
// widths have changed. Nothing is repainted.
void HeadedTable::updateColWidths()
{
    // Updating the internal state is only done in QtTableView::setOffset(),
    // so we are forced to the following contortion. The bug has been
    // reported (QtTableView::updateTableSize() should have done it but
    // doesn't).
    int xo = head->xOffset();
    head->setAutoUpdate(FALSE);
    head->setOffset(0, head->yOffset(), FALSE);
    head->setOffset(xo, head->yOffset(), FALSE);
    head->setAutoUpdate(TRUE);
    body->setAutoUpdate(FALSE);
    body->setOffset(0, body->yOffset(), FALSE);
    body->setOffset(xo, body->yOffset(), FALSE);
    body->setAutoUpdate(TRUE);
}

// move to top and repaint
void HeadedTable::topAndRepaint()
{
    body->setAutoUpdate(FALSE);
    body->setYOffset(0);
    body->setAutoUpdate(TRUE);
    repaintHead();
    repaintBody();
}

void HeadedTable::repaintAll()
{
    repaint();
    repaintHead();
    repaintBody();
}

// compute width of text in body cell with current font
int HeadedTable::bodyTextWidth(const char *s)
{
    return body->fm.width(s);
}

// compute width of text in heading with current font
int HeadedTable::headTextWidth(const char *s)
{
    return head->fm.width(s) + 4;
}

// clear (i.e. repaint) table to the right of all columns, if visible
void HeadedTable::clearRight()
{
    QRect vrh = head->viewRect();
    int lastx = head->totalWidth() - head->xOffset();
    if(lastx < vrh.width()) {
      head->repaint(lastx + vrh.x(), vrh.y(),
                  vrh.width() - lastx, vrh.height());
      QRect vrb = body->viewRect();
      body->repaint(lastx + vrb.x(), vrb.y(),
                  vrb.width() - lastx, vrb.height());
    }
}

// clear the are below all rows, if visible
void HeadedTable::clearBelow()
{
    QRect vr = body->viewRect();
    vr.setTop(body->totalHeight());
    body->repaint(vr);
}

// return actual (computed if necessary) column width of logical column
int HeadedTable::actualColWidth(int col)
{
    int w = widths[col];      // use cached first
    if(w < 0) {
      w = colWidth(col);
      if(w < 0)
          w = computedWidth(col);
      else {
          w *= body->zerowidth;
          int hw = headTextWidth(title(col));
          if(alignment(col) == AlignRight) hw += 4;
          if(hw > w) w = hw;
          if(treemode && physcols[col] == 0) {
            int maxdepth = 0;
            for(int i = 0; i < nrows; i++) {
                int d = rowDepth(i);
                if(d > maxdepth)
                  maxdepth = d;
            }
            w += gadget_space + maxdepth * treestep;
          } else
            w += leftGap(col);
      }
      widths[col] = w;
    }
    return w;
}

// determine width of a (logical) column. return true if column width
// actually changed (always true if no column width has been
// calculated)
bool HeadedTable::widthChanged(int col)
{
    if(colWidth(col) >= 0 && !treemode && physcols[col] != 0)
      return FALSE; // ordinary fixed columns never change
    int oldw = widths[col];
    widths[col] = -1;
    return oldw != actualColWidth(col);
}

int HeadedTable::computedWidth(int col)
{
    int w = 0;
    bool treecol = treemode && physcols[col] == 0;
    for(int i = 0; i < numRows(); i++) {
      int sw = bodyTextWidth(cachedText(i, col));
      if(treecol)
          sw += treestep * rowDepth(i);
      if(sw > w) w = sw;
    }
    // don't forget the width of the heading
    int hw = headTextWidth(title(col));
    if(hw > w) w = hw;
    w += leftGap(col);
    if(treecol)
      w += gadget_space;
    return w;
}

void HeadedTable::resetWidths()
{
    widths.clear();
    for(int i = 0; i < numCols(); i++)
      widths.set(i, -1);
}

void HeadedTable::setSelectionColors(QColor fg, QColor bg)
{
    sel_fg = fg;
    sel_bg = bg;
}



Generated by  Doxygen 1.6.0   Back to index