/* * $Id$ * * KTreeView implementation * * Copyright (C) 1997 Johannes Sixt * * based on KTreeList, which is * Copyright (C) 1996 Keith Brown and KtSoft * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABLILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. You should have received a copy * of the GNU General Public License along with this program; if not, write * to the Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ #include #include "ktreeview.moc" #include /* used for TQApplication::closingDown() */ #include /* used for keyboard interface */ #include /* used to paint items */ #include #include #include /* * ------------------------------------------------------------------- * * KTreeViewItem * * ------------------------------------------------------------------- */ // constructor KTreeViewItem::KTreeViewItem(const TQString& theText) : owner(0), numChildren(0), doExpandButton(true), expanded(false), delayedExpanding(false), doTree(true), doText(true), child(0), parent(0), sibling(0), deleteChildren(false) { text = theText; } // constructor that takes a pixmap KTreeViewItem::KTreeViewItem(const TQString& theText, const TQPixmap& thePixmap) : owner(0), numChildren(0), doExpandButton(true), expanded(false), delayedExpanding(false), doTree(true), doText(true), child(0), parent(0), sibling(0), deleteChildren(false) { text = theText; pixmap = thePixmap; } // destructor KTreeViewItem::~KTreeViewItem() { if (deleteChildren) { // remove the children KTreeViewItem* i = child; while (i) { KTreeViewItem* d = i; i = i->getSibling(); delete d; } } } // appends a direct child void KTreeViewItem::appendChild(KTreeViewItem* newChild) { newChild->parent = this; newChild->owner = owner; if (!getChild()) { child = newChild; } else { KTreeViewItem* lastChild = getChild(); while (lastChild->hasSibling()) { lastChild = lastChild->getSibling(); } lastChild->sibling = newChild; } newChild->sibling = 0; numChildren++; } // returns the bounding rectangle of the item content (not including indent // and branches) for hit testing TQRect KTreeViewItem::boundingRect(int indent) const { const TQFontMetrics& fm = owner->fontMetrics(); int rectX = indent; int rectY = 1; int rectW = width(indent, fm) - rectX; int rectH = height(fm) - 2; return TQRect(rectX, rectY, rectW, rectH); } // returns the child item at the specified index KTreeViewItem* KTreeViewItem::childAt(int index) const { if (!hasChild()) return 0; KTreeViewItem* item = getChild(); while (index > 0 && item != 0) { item = item->getSibling(); index--; } return item; } // returns the number of children this item has uint KTreeViewItem::childCount() const { return numChildren; } /* returns the index of the given child item in this item's branch */ int KTreeViewItem::childIndex(KTreeViewItem* searched) const { assert(searched != 0); int index = 0; KTreeViewItem* item = getChild(); while (item != 0 && item != searched) { item = item->getSibling(); index++; } return item == 0 ? -1 : index; } // indicates whether mouse press is in expand button inline bool KTreeViewItem::expandButtonClicked(const TQPoint& coord) const { return expandButton.contains(coord); } bool KTreeViewItem::mousePressEvent( const TQPoint& ) { return FALSE; } // returns a pointer to first child item KTreeViewItem* KTreeViewItem::getChild() const { return child; } // returns the parent of this item KTreeViewItem* KTreeViewItem::getParent() const { return parent; } // returns reference to the item pixmap const TQPixmap& KTreeViewItem::getPixmap() const { return pixmap; } // returns the sibling below this item KTreeViewItem* KTreeViewItem::getSibling() const { return sibling; } // returns a pointer to the item text const TQString& KTreeViewItem::getText() const { return text; } // indicates whether this item has any children bool KTreeViewItem::hasChild() const { return child != 0; } // indicates whether this item has a parent bool KTreeViewItem::hasParent() const { return parent != 0; } // indicates whether this item has a sibling below it bool KTreeViewItem::hasSibling() const { return sibling != 0; } int KTreeViewItem::height() const { assert(owner != 0); return height(owner->fontMetrics()); } // returns the maximum height of text and pixmap including margins // or -1 if item is empty -- SHOULD NEVER BE -1! int KTreeViewItem::height(const TQFontMetrics& fm) const { int maxHeight = pixmap.height(); int textHeight = fm.ascent() + fm.leading(); maxHeight = textHeight > maxHeight ? textHeight : maxHeight; return maxHeight == 0 ? -1 : maxHeight + 8; } // inserts child item at specified index in branch void KTreeViewItem::insertChild(int index, KTreeViewItem* newChild) { if (index < 0) { appendChild(newChild); return; } newChild->parent = this; newChild->owner = owner; if (index == 0) { newChild->sibling = getChild(); child = newChild; } else { KTreeViewItem* prevItem = getChild(); KTreeViewItem* nextItem = prevItem->getSibling(); while (--index > 0 && nextItem) { prevItem = nextItem; nextItem = prevItem->getSibling(); } prevItem->sibling = newChild; newChild->sibling = nextItem; } numChildren++; if ( owner ) { owner->updateVisibleItems(); owner->update(); } } // indicates whether this item is displayed expanded // NOTE: a TRUE response does not necessarily indicate the item // has any children bool KTreeViewItem::isExpanded() const { return expanded; } // returns true if all ancestors are expanded bool KTreeViewItem::isVisible() const { for (KTreeViewItem* p = getParent(); p != 0; p = p->getParent()) { if (!p->isExpanded()) return false; } return true; } // paint this item, including tree branches and expand button void KTreeViewItem::paint(TQPainter* p, int indent, const TQColorGroup& cg, bool highlighted) const { assert(getParent() != 0); /* can't paint root item */ p->save(); p->setPen(cg.text()); p->setBackgroundColor(cg.base()); int cellHeight = height(p->fontMetrics()); if (doTree) { paintTree(p, indent, cellHeight); } /* * If this item has at least one child and expand button drawing is * enabled, set the rect for the expand button for later mouse press * bounds checking, and draw the button. */ if (doExpandButton && (child || delayedExpanding)) { paintExpandButton(p, indent, cellHeight); } // now draw the item pixmap and text, if applicable int pixmapX = indent; int pixmapY = (cellHeight - pixmap.height()) / 2; p->drawPixmap(pixmapX, pixmapY, pixmap); if (doText) { paintText(p, indent, cellHeight, cg, highlighted); } p->restore(); } void KTreeViewItem::paintExpandButton(TQPainter* p, int indent, int cellHeight) const { int parentLeaderX = indent - (22 / 2); int cellCenterY = cellHeight / 2; expandButton.setRect(parentLeaderX - 4, cellCenterY - 4, 9, 9); p->setBrush(TQt::white); p->drawRect(expandButton); if (expanded) { p->drawLine(parentLeaderX - 2, cellCenterY, parentLeaderX + 2, cellCenterY); } else { p->drawLine(parentLeaderX - 2, cellCenterY, parentLeaderX + 2, cellCenterY); p->drawLine(parentLeaderX, cellCenterY - 2, parentLeaderX, cellCenterY + 2); } p->setBrush(TQt::NoBrush); } // paint the highlight void KTreeViewItem::paintHighlight(TQPainter* p, int indent, const TQColorGroup& colorGroup, bool hasFocus, TQt::GUIStyle style) const { TQColor fc; if (style == TQt::WindowsStyle) fc = TQt::darkBlue; /* hardcoded in TQt */ else fc = colorGroup.text(); TQRect textRect = textBoundingRect(indent); int t,l,b,r; textRect.coords(&l, &t, &r, &b); p->fillRect(textRect, fc); /* draw highlight background */ if (hasFocus) { if(style == TQt::WindowsStyle) { /* draw Windows style highlight */ textRect.setCoords(l - 1, t - 1, r + 1, b + 1); p->setPen(TQPen(TQt::yellow, 0, TQt::DotLine)); p->setBackgroundColor(fc); p->setBackgroundMode(Qt::OpaqueMode); p->drawRect(textRect); textRect.setCoords(l - 2, t - 2, r + 2, b + 2); p->setPen(fc); p->drawRect(textRect); } else { /* draw Motif style highlight */ textRect.setCoords(l - 2, t - 2, r + 2, b + 2); p->drawRect(textRect); } } } // draw the text, highlighted if requested void KTreeViewItem::paintText(TQPainter* p, int indent, int cellHeight, const TQColorGroup& cg, bool highlighted) const { int textX = indent + pixmap.width() + 4; int textY = cellHeight - ((cellHeight - p->fontMetrics().ascent() - p->fontMetrics().leading()) / 2); if (highlighted) { paintHighlight(p, indent, cg, owner->hasFocus(), (TQt::GUIStyle)owner->style().styleHint(TQStyle::SH_GUIStyle)); // TQt3 doesn't make this easy ;) p->setPen(cg.base()); p->setBackgroundColor(cg.text()); } else { p->setPen(cg.text()); p->setBackgroundColor(cg.base()); } p->drawText(textX, textY, text); } // paint the tree structure void KTreeViewItem::paintTree(TQPainter* p, int indent, int cellHeight) const { int parentLeaderX = indent - (22 / 2); int cellCenterY = cellHeight / 2; int cellBottomY = cellHeight - 1; int itemLeaderX = indent - 3; /* * If this is not the first item in the tree draw the line up * towards parent or sibling. */ if (parent->parent != 0 || parent->getChild() != this) p->drawLine(parentLeaderX, 0, parentLeaderX, cellCenterY); // draw the line down towards sibling if (sibling) p->drawLine(parentLeaderX, cellCenterY, parentLeaderX, cellBottomY); /* * If this item has children or siblings in the tree or is a child of * an item other than the root item then draw the little line from the * item out to the left. */ if (sibling || (doExpandButton && (child || delayedExpanding)) || parent->parent != 0 || /* * The following handles the case of an item at the end of the * topmost level which doesn't have children. */ parent->getChild() != this) { p->drawLine(parentLeaderX, cellCenterY, itemLeaderX, cellCenterY); } /* * If there are siblings of ancestors below, draw our portion of the * branches that extend through this cell. */ KTreeViewItem* prevRoot = parent; while (prevRoot->getParent() != 0) { /* while not root item */ if (prevRoot->hasSibling()) { int sibLeaderX = owner->indentation(prevRoot) - (22 / 2); p->drawLine(sibLeaderX, 0, sibLeaderX, cellBottomY); } prevRoot = prevRoot->getParent(); } } // removes the given (direct) child from the branch bool KTreeViewItem::removeChild(KTreeViewItem* theChild) { // search item in list of children KTreeViewItem* prevItem = 0; KTreeViewItem* toRemove = getChild(); while (toRemove && toRemove != theChild) { prevItem = toRemove; toRemove = toRemove->getSibling(); } if (toRemove) { // found it! if (prevItem == 0) { child = toRemove->getSibling(); } else { prevItem->sibling = toRemove->getSibling(); } numChildren--; toRemove->owner = 0; } assert((numChildren == 0) == (child == 0)); if ( owner ) { owner->updateVisibleItems(); owner->update(); } return toRemove != 0; } // sets the delayed-expanding flag void KTreeViewItem::setDelayedExpanding(bool flag) { delayedExpanding = flag; } // tells the item whether it shall delete child items void KTreeViewItem::setDeleteChildren(bool flag) { deleteChildren = flag; } // sets the draw expand button flag of this item void KTreeViewItem::setDrawExpandButton(bool doit) { doExpandButton = doit; } // sets the draw text flag of this item void KTreeViewItem::setDrawText(bool doit) { doText = doit; } // sets the draw tree branch flag of this item void KTreeViewItem::setDrawTree(bool doit) { doTree = doit; } // sets the expanded flag of this item void KTreeViewItem::setExpanded(bool is) { expanded = is; if ( owner ) { owner->updateVisibleItems(); owner->update(); } } // sets the item pixmap to the given pixmap void KTreeViewItem::setPixmap(const TQPixmap& pm) { pixmap = pm; if ( owner ) { owner->updateVisibleItems(); owner->update(); } } // sets the item text to the given string void KTreeViewItem::setText(const TQString& t) { text = t; if ( owner ) { owner->updateVisibleItems(); owner->update(); } } // counts the child items and stores the result in numChildren void KTreeViewItem::synchNumChildren() { numChildren = 0; KTreeViewItem* item = getChild(); while (item != 0) { numChildren++; item = item->getSibling(); } } /* * returns the bounding rect of the item text in cell coordinates couldn't * get TQFontMetrics::boundingRect() to work right so I made my own */ TQRect KTreeViewItem::textBoundingRect(int indent) const { const TQFontMetrics& fm = owner->fontMetrics(); int cellHeight = height(fm); int rectX = indent + pixmap.width() + 3; int rectY = (cellHeight - fm.ascent() - fm.leading()) / 2 + 2; int rectW = fm.width(text) + 1; int rectH = fm.ascent() + fm.leading(); return TQRect(rectX, rectY, rectW, rectH); } // returns the total width of text and pixmap, including margins, spacing // and indent, or -1 if empty -- SHOULD NEVER BE -1! int KTreeViewItem::width(int indent) const { return width(indent, owner->fontMetrics()); } int KTreeViewItem::width(int indent, const TQFontMetrics& fm) const { int maxWidth = pixmap.width(); maxWidth += (4 + fm.width(text)); return maxWidth == 0 ? -1 : indent + maxWidth + 3; } /* * ------------------------------------------------------------------- * * KTreeView * * ------------------------------------------------------------------- */ // constructor KTreeView::KTreeView(TQWidget *parent, const char *name, WFlags f) : TQGridView(parent, name, f), clearing(false), current(-1), drawExpandButton(true), drawTree(true), expansion(0), goingDown(false), itemIndent(22), showText(true), itemCapacity(500), visibleItems(0), rubberband_mode(false) { setCellHeight(0); // setCellWidth(0); setNumRows(0); setNumCols(1); setVScrollBarMode(Auto); setHScrollBarMode(Auto); //switch(style().guiStyle()) { //case WindowsStyle: //case MotifStyle: setFrameStyle(TQFrame::WinPanel | TQFrame::Sunken); setBackgroundColor(colorGroup().base()); //break; /*default: setFrameStyle(TQFrame::Panel | TQFrame::Plain); setLineWidth(1); }*/ // setAcceptFocus(true); treeRoot = new KTreeViewItem; treeRoot->setExpanded(true); treeRoot->owner = this; visibleItems = new KTreeViewItem*[itemCapacity]; // clear those pointers for (int j = itemCapacity-1; j >= 0; j--) { visibleItems[j] = 0; } } // destructor KTreeView::~KTreeView() { goingDown = true; clear(); delete[] visibleItems; delete treeRoot; } // appends a child to the item at the given index with the given text // and pixmap void KTreeView::appendChildItem(const TQString & theText, const TQPixmap& thePixmap, int index) { KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); item->setDeleteChildren(true); appendChildItem(item, index); } // appends a child to the item at the end of the given path with // the given text and pixmap void KTreeView::appendChildItem(const TQString & theText, const TQPixmap& thePixmap, const KPath& thePath) { KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); item->setDeleteChildren(true); appendChildItem(item, thePath); } // appends the given item to the children of the item at the given index void KTreeView::appendChildItem(KTreeViewItem* newItem, int index) { /* find parent item and append new item to parent's sub tree */ KTreeViewItem* parentItem = itemAt(index); if (!parentItem) return; appendChildItem(parentItem, newItem); } // appends the given item to the children of the item at the end of the // given path void KTreeView::appendChildItem(KTreeViewItem* newItem, const KPath& thePath) { /* find parent item and append new item to parent's sub tree */ KTreeViewItem* parentItem = itemAt(thePath); if (!parentItem) return; appendChildItem(parentItem, newItem); } // indicates whether horizontal scrollbar appears only when needed bool KTreeView::autoBottomScrollBar() const { return (hScrollBarMode() == Auto); } // indicates whether vertical scrollbar appears only when needed bool KTreeView::autoScrollBar() const { return (vScrollBarMode() == Auto); } // indicates whether display updates automatically on changes bool KTreeView::autoUpdate() const { return isUpdatesEnabled(); } // indicates whether horizontal scrollbar is present bool KTreeView::bottomScrollBar() const { return !(horizontalScrollBar()->isHidden()); } // find item at specified index and change pixmap and/or text void KTreeView::changeItem(const TQString & newText, const TQPixmap *newPixmap, int index) { KTreeViewItem *item = itemAt(index); if(item) changeItem(item, index, newText, newPixmap); } // find item at end of specified path, and change pixmap and/or text void KTreeView::changeItem(const TQString & newText, const TQPixmap* newPixmap, const KPath& thePath) { KTreeViewItem* item = itemAt(thePath); if (item) { int index = itemRow(item); changeItem(item, index, newText, newPixmap); } } // clear all items from list and erase display void KTreeView::clear() { setCurrentItem(-1); /* somewhat of a hack for takeItem so it doesn't update the current item... */ clearing = TRUE; bool autoU = autoUpdate(); setAutoUpdate(FALSE); TQPtrStack stack; stack.push(treeRoot); while(!stack.isEmpty()) { KTreeViewItem *item = stack.pop(); if(item->hasChild()) { stack.push(item); stack.push(item->getChild()); } else if(item->hasSibling()) { stack.push(item); stack.push(item->getSibling()); } else if(item->getParent() != 0) { takeItem(item); delete item; } } clearing = FALSE; if(goingDown || TQApplication::closingDown()) return; if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } // return a count of all the items in the tree, whether visible or not uint KTreeView::count() { int total = 0; forEveryItem(&KTreeView::countItem, (void *)&total); return total; } // returns the index of the current (highlighted) item int KTreeView::currentItem() const { return current; } // only collapses the item if it is expanded. If not expanded, or // index invalid, does nothing void KTreeView::collapseItem(int index) { KTreeViewItem *item = itemAt(index); if(item && item->isExpanded()) expandOrCollapse(item); } // only expands the item if it is collapsed. If not collapsed, or // index invalid, does nothing void KTreeView::expandItem(int index) { KTreeViewItem *item = itemAt(index); if(item && !item->isExpanded()) expandOrCollapse(item); } // returns the depth the tree is automatically expanded to when // items are added int KTreeView::expandLevel() const { return expansion; } // expands or collapses the subtree rooted at the given item, // as approptiate // if item has no children, does nothing void KTreeView::expandOrCollapseItem(int index) { KTreeViewItem *item = itemAt(index); if(item) expandOrCollapse(item); } // visits every item in the tree, visible or not and applies // the user supplied function with the item and user data passed as parameters // if user supplied function returns true, traversal ends and function returns bool KTreeView::forEveryItem(KForEvery func, void* user, KTreeViewItem* item) { if (item == 0) { item = treeRoot; } assert(item->owner == this); item = item->getChild(); while (item != 0) { // visit the siblings if ((*func)(item, user)) { return true; } // visit the children (recursively) if (item->hasChild()) { if (forEveryItem(func, user, item)) return true; } item = item->getSibling(); } return false; } // visits every visible item in the tree in order and applies the // user supplied function with the item and user data passed as parameters // if user supplied function returns TRUE, traversal ends and function // returns bool KTreeView::forEveryVisibleItem(KForEvery func, void *user, KTreeViewItem* item) { if (item == 0) { item = treeRoot; } else { // children are invisible (hence, nothing to do) // if item is invisible or collapsed if (!item->isVisible() || !item->isExpanded()) { return false; } } assert(item->owner == this); item = item->getChild(); while (item != 0) { // visit the siblings if ((*func)(item, user)) { return true; } // visit the children (recursively) if (item->hasChild() && item->isExpanded()) { if (forEveryVisibleItem(func, user, item)) return true; } item = item->getSibling(); } return false; } // returns a pointer to the KTreeViewItem at the current index // or 0 if no current item KTreeViewItem *KTreeView::getCurrentItem() { if(current == -1) return 0; return itemAt(current); } // returns the current indent spacing int KTreeView::indentSpacing() { return itemIndent; } // inserts a new item with the specified text and pixmap before // or after the item at the given index, depending on the value // of prefix // if index is negative, appends item to tree at root level bool KTreeView::insertItem(const TQString & theText, const TQPixmap& thePixmap, int row, bool prefix) { KTreeViewItem* refItem = itemAt(row); KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); item->setDeleteChildren(true); bool success = insertItem(refItem, item, prefix); if (!success) delete item; return success; } // inserts a new item with the specified text and pixmap before // or after the item at the end of the given path, depending on the value // of prefix bool KTreeView::insertItem(const TQString & theText, const TQPixmap& thePixmap, const KPath& path, bool prefix) { KTreeViewItem* refItem = itemAt(path); KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); item->setDeleteChildren(true); bool success = insertItem(refItem, item, prefix); if (!success) delete item; return success; } // inserts the given item or derived object into the tree before // or after the item at the given index, depending on the value // of prefix // if index is negative, appends item to tree at root level bool KTreeView::insertItem(KTreeViewItem* newItem, int index, bool prefix) { // find the item currently at the index, if there is one KTreeViewItem* refItem = itemAt(index); // insert new item at the appropriate place return insertItem(refItem, newItem, prefix); } // inserts the given item or derived object into the tree before // or after the item at the end of the given path, depending on the value // of prefix bool KTreeView::insertItem(KTreeViewItem* newItem, const KPath& thePath, bool prefix) { // find the item currently at the end of the path, if there is one KTreeViewItem* refItem = itemAt(thePath); // insert new item at appropriate place return insertItem(refItem, newItem, prefix); } /* * returns pointer to KTreeViewItem at the specifed row or 0 if row is out * of limits. */ KTreeViewItem* KTreeView::itemAt(int row) { if (row < 0 || row >= numRows()) { return 0; } else { // lookup the item in the list of visible items assert(row < itemCapacity); KTreeViewItem* i = visibleItems[row]; assert(i != 0); return i; } } // returns pointer to KTreeViewItem at the end of the // path or 0 if not found KTreeViewItem* KTreeView::itemAt(const KPath& path) { if (path.isEmpty()) return 0; // need a copy of the path because recursiveFind will destroy it KPath pathCopy; pathCopy.setAutoDelete(false); pathCopy = path; return recursiveFind(pathCopy); } // computes the path of the item at the specified index // if index is invalid, nothing is done. void KTreeView::itemPath(int row, KPath& path) { KTreeViewItem* item = itemAt(row); if (item != 0) { itemPath(item, path); } } // returns the row in the visible tree of the given item or // -1 if not found int KTreeView::itemRow(KTreeViewItem* item) { if (item->owner == this) { // search in list of visible items for (int i = numRows()-1; i >= 0; i--) { if (visibleItems[i] == item) { return i; } } } // not found return -1; } /* * move the subtree at the specified index up one branch level (make root * item a sibling of its current parent) */ void KTreeView::join(int index) { KTreeViewItem *item = itemAt(index); if(item) join(item); } /* * move the subtree at the specified index up one branch level (make root * item a sibling of it's current parent) */ void KTreeView::join(const KPath& path) { KTreeViewItem *item = itemAt(path); if (item) join(item); } /* move item at specified index one slot down in its parent's sub tree */ void KTreeView::lowerItem(int index) { KTreeViewItem *item = itemAt(index); if(item) lowerItem(item); } /* move item at specified path one slot down in its parent's sub tree */ void KTreeView::lowerItem(const KPath& path) { KTreeViewItem* item = itemAt(path); if (item) lowerItem(item); } /* move item at specified index one slot up in its parent's sub tree */ void KTreeView::raiseItem(int index) { KTreeViewItem* item = itemAt(index); if (item) raiseItem(item); } /* move item at specified path one slot up in its parent's sub tree */ void KTreeView::raiseItem(const KPath& path) { KTreeViewItem* item = itemAt(path); if (item) raiseItem(item); } // remove the item at the specified index and delete it void KTreeView::removeItem(int index) { KTreeViewItem *item = itemAt(index); if(item) { takeItem(item); delete item; } } // remove the item at the end of the specified path and delete it void KTreeView::removeItem(const KPath& thePath) { KTreeViewItem* item = itemAt(thePath); if (item) { takeItem(item); delete item; } } // indicates whether vertical scrollbar is present bool KTreeView::scrollBar() const { return !(verticalScrollBar()->isHidden()); } // enables/disables auto update of display void KTreeView::setAutoUpdate(bool enable) { setUpdatesEnabled(enable); } // enables/disables horizontal scrollbar void KTreeView::setBottomScrollBar(bool enable) { setHScrollBarMode(enable ? AlwaysOn : AlwaysOff); } // sets the current item and hightlights it, emitting signals void KTreeView::setCurrentItem(int row) { if (row == current) return; int numVisible = numRows(); if (row > numVisible) { emit highlighted( current ); return; } int oldCurrent = current; current = row; if (oldCurrent < numVisible) updateCell(oldCurrent, 0); if (current > -1) { updateCell(current, 0); } emit highlighted( current ); } // enables/disables drawing of expand button void KTreeView::setExpandButtonDrawing(bool enable) { if(drawExpandButton == enable) return; drawExpandButton = enable; forEveryItem(&KTreeView::setItemExpandButtonDrawing, 0); if(autoUpdate() && isVisible()) repaint(); } // sets depth to which subtrees are automatically expanded, and // redraws tree if auto update enabled void KTreeView::setExpandLevel(int level) { if(expansion == level) return; expansion = level; KTreeViewItem *item = getCurrentItem(); forEveryItem(&KTreeView::setItemExpanded, 0); while(item) { if(item->getParent()->isExpanded()) break; item = item->getParent(); } setCurrentItem(itemRow(item)); if(autoUpdate() && isVisible()) repaint(); } // sets the indent margin for all branches and repaints if auto update enabled void KTreeView::setIndentSpacing(int spacing) { if (itemIndent == spacing) return; itemIndent = spacing; updateCellWidth(); if (autoUpdate() && isVisible()) repaint(); } // enables/disables vertical scrollbar void KTreeView::setScrollBar(bool enable) { setVScrollBarMode(enable? AlwaysOn : AlwaysOff ); } // enables/disables display of item text (keys) void KTreeView::setShowItemText(bool enable) { if(showText == enable) return; showText = enable; forEveryItem(&KTreeView::setItemShowText, 0); if(autoUpdate() && isVisible()) repaint(); } // indicates whether vertical scrolling is by pixel or row void KTreeView::setSmoothScrolling(bool enable) { verticalScrollBar()->setLineStep(enable ? 1 : cellHeight()); } // enables/disables tree branch drawing void KTreeView::setTreeDrawing(bool enable) { if(drawTree == enable) return; drawTree = enable; forEveryItem(&KTreeView::setItemTreeDrawing, 0); if(autoUpdate() && isVisible()) repaint(); } // indicates whether item text keys are displayed bool KTreeView::showItemText() const { return showText; } // indicates whether scrolling is by pixel or row bool KTreeView::smoothScrolling() const { return (verticalScrollBar()->lineStep() == 1); } // indents the item at the given index, splitting the tree into // a new branch void KTreeView::split(int index) { KTreeViewItem *item = itemAt(index); if(item) split(item); } // indents the item at the given path, splitting the tree into // a new branch void KTreeView::split(const KPath& path) { KTreeViewItem* item = itemAt(path); if (item) split(item); } // removes item at specified index from tree but does not delete it // returns pointer to the item or 0 if not succesful KTreeViewItem *KTreeView::takeItem(int index) { KTreeViewItem *item = itemAt(index); if(item) takeItem(item); return item; } // removes item at specified path from tree but does not delete it // returns pointer to the item or 0 if not successful KTreeViewItem* KTreeView::takeItem(const KPath& path) { KTreeViewItem* item = itemAt(path); if (item) takeItem(item); return item; } // indicates whether tree branches are drawn bool KTreeView::treeDrawing() const { return drawTree; } // appends a child to the specified parent item (note: a child, not a sibling, is added!) void KTreeView::appendChildItem(KTreeViewItem* theParent, KTreeViewItem* newItem) { theParent->appendChild(newItem); // set item state newItem->setDrawExpandButton(drawExpandButton); newItem->setDrawTree(drawTree); newItem->setDrawText(showText); if (level(newItem) < expansion) { newItem->setExpanded(true); } // fix up branch levels of any children that the new item may already have if(newItem->hasChild()) { fixChildren(newItem); } // if necessary, adjust cell width, number of rows and repaint if (newItem->isVisible() || theParent->childCount() == 1) { bool autoU = autoUpdate(); setAutoUpdate(false); updateVisibleItems(); if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } } // changes the given item with the new text and/or pixmap void KTreeView::changeItem(KTreeViewItem* toChange, int itemRow, const TQString & newText, const TQPixmap* newPixmap) { int indent = indentation(toChange); int oldWidth = toChange->width(indent); if(!newText.isNull()) toChange->setText(newText); if (newPixmap) toChange->setPixmap(*newPixmap); if(oldWidth != toChange->width(indent)) updateCellWidth(); if(itemRow == -1) return; if(autoUpdate()) updateCell(itemRow, 0); } // collapses the subtree at the specified item void KTreeView::collapseSubTree(KTreeViewItem* subRoot) { assert(subRoot->owner == this); // must move the current item if it is visible KTreeViewItem* cur = current >= 0 ? itemAt(current) : 0; subRoot->setExpanded(false); if (subRoot->isVisible()) { updateVisibleItems(); // re-seat current item if (cur != 0) { setCurrentItem(itemRow(cur)); } } // roland repaint(); setAutoUpdate(TRUE); // roland } // used by count() with forEach() function to count total number // of items in the tree bool KTreeView::countItem(KTreeViewItem*, void* total) { int* t = static_cast(total); (*t)++; return false; } // if item is expanded, collapses it or vice-versa void KTreeView::expandOrCollapse(KTreeViewItem* parent) { bool autoU = autoUpdate(); setAutoUpdate(false); int parentIndex = itemRow(parent); if (parent->isExpanded()) { collapseSubTree(parent); emit collapsed(parentIndex); } else { expandSubTree(parent); emit expanded(parentIndex); } if (autoU && isVisible()) repaint(); setAutoUpdate(autoU); } // expands the subtree at the given item void KTreeView::expandSubTree(KTreeViewItem* subRoot) { assert(subRoot->owner == this); // must move the current item if it is visible KTreeViewItem* cur = current >= 0 ? itemAt(current) : 0; bool allow = true; if (subRoot->delayedExpanding) { emit expanding(subRoot, allow); } if (!allow) return; subRoot->setExpanded(true); if (subRoot->isVisible()) { updateVisibleItems(); // re-seat current item if (cur != 0) { setCurrentItem(itemRow(cur)); } } // roland repaint(); setAutoUpdate(TRUE); // roland } // fix up branch levels out of whack from split/join operations on the tree void KTreeView::fixChildren(KTreeViewItem *parentItem) { KTreeViewItem* childItem = 0; KTreeViewItem* siblingItem = 0; // int childBranch = parentItem->getBranch() + 1; if(parentItem->hasChild()) { childItem = parentItem->getChild(); // childItem->setBranch(childBranch); childItem->owner = parentItem->owner; fixChildren(childItem); } while(childItem && childItem->hasSibling()) { siblingItem = childItem->getSibling(); // siblingItem->setBranch(childBranch); siblingItem->owner = parentItem->owner; fixChildren(siblingItem); childItem = siblingItem; } } // handles TQFocusEvent processing by setting current item to top // row if there is no current item, and updates cell to add or // delete the focus rectangle on the highlight bar void KTreeView::focusInEvent(TQFocusEvent *) { if(current < 0 && numRows() > 0) setCurrentItem(rowAt(contentsY())); updateCell(current, 0); } // visits every item in the tree, visible or not and applies the user // supplied member function with the item and user data passed as parameters // if the user supplied member function returns TRUE, traversal // ends and the function returns void KTreeView::forEveryItem(KForEveryM func, void *user) { KTreeViewItem *item = treeRoot->getChild(); TQPtrStack stack; while(item) { stack.push(item); while(!stack.isEmpty()) { KTreeViewItem *poppedItem = stack.pop(); if((this->*func)(poppedItem, user)) return; if(poppedItem->hasChild()) { KTreeViewItem *childItem = poppedItem->getChild(); while(childItem) { stack.push(childItem); childItem = childItem->getSibling(); } } } item = item->getSibling(); } } // visits every visible item in the tree in order and applies the user // supplied member function with the item and user data passed as parameters // if user supplied function returns TRUE, traversal ends and function // returns void KTreeView::forEveryVisibleItem(KForEveryM func, void *user) { TQPtrStack stack; KTreeViewItem *item = treeRoot->getChild(); do { while(item) { if((this->*func)(item, user)) return; if(item->hasChild() && item->isExpanded()) { stack.push(item); item = item->getChild(); } else item = item->getSibling(); } if(stack.isEmpty()) break; item = stack.pop()->getSibling(); } while(TRUE); } // called by updateCellWidth() for each item in the visible list bool KTreeView::getMaxItemWidth(KTreeViewItem *item, void *user) { int indent = indentation(item); int* maxW = static_cast(user); int w = item->width(indent); if (w > *maxW) *maxW = w; return false; } int KTreeView::indentation(KTreeViewItem* item) const { return level(item) * itemIndent + itemIndent + 3; } // inserts the new item before or after the reference item, depending // on the value of prefix bool KTreeView::insertItem(KTreeViewItem* referenceItem, KTreeViewItem* newItem, bool prefix) { assert(newItem != 0); assert(referenceItem == 0 || referenceItem->owner == this); /* set the new item's state */ newItem->setDrawExpandButton(drawExpandButton); newItem->setDrawTree(drawTree); newItem->setDrawText(showText); if (cellHeight() == 0) { setCellHeight(newItem->height(fontMetrics())); } KTreeViewItem* parentItem; if (referenceItem) { parentItem = referenceItem->getParent(); int insertIndex = parentItem->childIndex(referenceItem); if (!prefix) insertIndex++; parentItem->insertChild(insertIndex, newItem); } else { // no reference item, append at end of visible tree // need to repaint parentItem = treeRoot; parentItem->appendChild(newItem); } // set item expansion if (level(newItem) < expansion) newItem->setExpanded(true); // fix up branch levels of any children if (newItem->hasChild()) fixChildren(newItem); // if repaint necessary, do it if visible and auto update // enabled if (newItem->isVisible() || parentItem->childCount() == 1) { bool autoU = autoUpdate(); setAutoUpdate(FALSE); updateVisibleItems(); if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } return true; } /* * returns pointer to item's path */ void KTreeView::itemPath(KTreeViewItem* item, KPath& path) const { assert(item != 0); assert(item->owner == this); if (item != treeRoot) { itemPath(item->getParent(), path); path.push(new TQString(item->getText())); } } /* * joins the item's branch into the tree, making the item a sibling of its * parent */ void KTreeView::join(KTreeViewItem *item) { KTreeViewItem *itemParent = item->getParent(); if(itemParent->hasParent()) { bool autoU = autoUpdate(); setAutoUpdate(FALSE); takeItem(item); insertItem(itemParent, item, FALSE); if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } } // handles keyboard interface to tree list void KTreeView::keyPressEvent(TQKeyEvent *e) { if(numRows() == 0) // nothing to be done return; if(currentItem() < 0) // if no current item, make the top item current setCurrentItem(rowAt(contentsY())); int pageSize, delta; switch(e->key()) { case Key_Up: // make previous item current, scroll up if necessary if(currentItem() > 0) { setCurrentItem(currentItem() - 1); ensureCellVisible(currentItem(), 0); } break; case Key_Down: // make next item current, scroll down if necessary if (currentItem() < numRows() - 1) { setCurrentItem(currentItem() + 1); ensureCellVisible(currentItem(), 0); } break; case Key_Next: // move highlight one page down and scroll down delta = TQMAX(1, visibleHeight() / cellHeight()); setCurrentItem(TQMIN(currentItem() + delta, numRows() - 1)); ensureCellVisible(currentItem(), 0); break; case Key_Prior: // move highlight one page up and scroll up delta = TQMAX(1, visibleHeight() / cellHeight()); setCurrentItem(TQMAX(currentItem() - delta, 0)); ensureCellVisible(currentItem(), 0); break; case Key_Plus: // if current item has subtree and is collapsed, expand it if(currentItem() >= 0) expandItem(currentItem()); break; case Key_Minus: // if current item has subtree and is expanded, collapse it if(currentItem() >= 0) collapseItem(currentItem()); break; case Key_Return: case Key_Enter: // select the current item if(currentItem() >= 0) emit selected(currentItem()); break; default: break; } } int KTreeView::level(KTreeViewItem* item) const { assert(item != 0); assert(item->owner == this); assert(item != treeRoot); int l = 0; item = item->parent->parent; /* since item != treeRoot, there is a parent */ while (item != 0) { item = item->parent; l++; } return l; } /* move specified item down one slot in parent's subtree */ void KTreeView::lowerItem(KTreeViewItem *item) { KTreeViewItem *itemParent = item->getParent(); uint itemChildIndex = itemParent->childIndex(item); if(itemChildIndex < itemParent->childCount() - 1) { bool autoU = autoUpdate(); setAutoUpdate(FALSE); takeItem(item); insertItem(itemParent->childAt(itemChildIndex), item, FALSE); if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } } // handle mouse double click events by selecting the clicked item // and emitting the signal void KTreeView::mouseDoubleClickEvent(TQMouseEvent *e) { // find out which row has been clicked TQPoint mouseCoord = viewportToContents(e->pos()); int itemClicked = rowAt(mouseCoord.y()); // if a valid row was not clicked, do nothing if(itemClicked == -1) return; KTreeViewItem *item = itemAt(itemClicked); if(!item) return; // translate mouse coord to cell coord int cellY = cellHeight()*itemClicked; int cellX = 0; TQPoint cellCoord(mouseCoord.x() - cellX, mouseCoord.y() - cellY); // hit test item int indent = indentation(item); if(item->boundingRect(indent).contains(cellCoord)) emit selected(itemClicked); } // handle mouse movement events void KTreeView::mouseMoveEvent(TQMouseEvent *e) { // in rubberband_mode we actually scroll the window now if (rubberband_mode) { move_rubberband(e->pos()); } } // handle single mouse presses void KTreeView::mousePressEvent(TQMouseEvent *e) { // first: check which button was pressed if (e->button() == Qt::MidButton) { // RB: the MMB is hardcoded to the "rubberband" scroll mode if (!rubberband_mode) { start_rubberband(e->pos()); } return; } else if ( ( rubberband_mode ) && ( e->button() != Qt::RightButton ) ) { // another button was pressed while rubberbanding, stop the move. // RB: if we allow other buttons while rubberbanding the tree can expand // while rubberbanding - we then need to reclaculate and resize the // rubberband rect and show the new size end_rubberband(); return; // should we process the button press? } // find out which row has been clicked TQPoint mouseCoord = viewportToContents(e->pos()); int itemClicked = rowAt(mouseCoord.y()); // nothing to do if not on valid row if (itemClicked == -1) return; KTreeViewItem* item = itemAt(itemClicked); if (!item) return; // translate mouse coord to cell coord int cellX = 0; int cellY = cellHeight()*itemClicked; TQPoint cellCoord(mouseCoord.x() - cellX, mouseCoord.y() - cellY); /* hit expand button (doesn't set currentItem) */ if(item->expandButtonClicked(cellCoord)) { expandOrCollapse(item); } // hit select button (to select/deselect the item for backup) // 2002-01-20 LEW: I'm adding the emit() here so that the screen gets updated, // as in KTreeView::mouseDoubleClickEvent(). KTreeViewItem::mousePressEvent() // returns false, so I guess some other function is being called here. else if ( item->mousePressEvent( cellCoord ) ) { // Item processed the button press in localSelected(itemClicked) emit selected(itemClicked); } // hit item (to show info on the file/dir label clicked) else if (item->boundingRect(indentation(item)).contains(cellCoord)) { setCurrentItem(itemClicked); // highlight item if ( e->button() == Qt::RightButton ) { emit popupMenu( itemClicked, mapToGlobal( TQPoint( e->pos().x(), e->pos().y() ) ) ); } } } // handle mouse release events void KTreeView::mouseReleaseEvent(TQMouseEvent *e) { /* if it's the MMB end rubberbanding */ if (rubberband_mode && e->button()==Qt::MidButton) { end_rubberband(); } } // rubberband move: draw the rubberband void KTreeView::draw_rubberband() { #if 0 /* * RB: I'm using a white pen because of the XorROP mode. I would prefer * to draw the rectangle in red but I don't now how to get a pen which * draws red in XorROP mode (this depends on the background). In fact * the color should be configurable. */ if (!rubberband_mode) return; TQPainter paint(this); paint.setPen(white); paint.setRasterOp(XorROP); paint.drawRect(xOffset()*viewWidth()/totalWidth(), yOffset()*viewHeight()/totalHeight(), rubber_width+1, rubber_height+1); paint.end(); #endif } // rubberband move: start move void KTreeView::start_rubberband(const TQPoint& where) { #if 0 if (rubberband_mode) { // Oops! end_rubberband(); } /* RB: Don't now, if this check is necessary */ if (!viewWidth() || !viewHeight()) return; if (!totalWidth() || !totalHeight()) return; // calculate the size of the rubberband rectangle rubber_width = viewWidth()*viewWidth()/totalWidth(); if (rubber_width > viewWidth()) rubber_width = viewWidth(); rubber_height = viewHeight()*viewHeight()/totalHeight(); if (rubber_height > viewHeight()) rubber_height = viewHeight(); // remember the cursor position and the actual offset rubber_startMouse = where; rubber_startX = xOffset(); rubber_startY = yOffset(); rubberband_mode=TRUE; draw_rubberband(); #endif } // rubberband move: end move void KTreeView::end_rubberband() { #if 0 if (!rubberband_mode) return; draw_rubberband(); rubberband_mode = FALSE; #endif } // rubberband move: handle mouse moves void KTreeView::move_rubberband(const TQPoint& where) { #if 0 if (!rubberband_mode) return; // look how much the mouse moved and calc the new scroll position TQPoint delta = where - rubber_startMouse; int nx = rubber_startX + delta.x() * totalWidth() / viewWidth(); int ny = rubber_startY + delta.y() * totalHeight() / viewHeight(); // check the new position (and make it valid) if (nx < 0) nx = 0; else if (nx > maxXOffset()) nx = maxXOffset(); if (ny < 0) ny = 0; else if (ny > maxYOffset()) ny = maxYOffset(); // redraw the rubberband at the new position draw_rubberband(); setOffset(nx,ny); draw_rubberband(); #endif } // paints the cell at the specified row and col // col is ignored for now since there is only one void KTreeView::paintCell(TQPainter* p, int row, int) { KTreeViewItem* item = itemAt(row); if (item == 0) return; p->setClipRect(cellRect(), TQPainter::CoordPainter ); TQColorGroup cg = colorGroup(); int indent = indentation(item); item->paint(p, indent, cg, current == row); /* highlighted */ p->setClipping(false); } /* raise the specified item up one slot in parent's subtree */ void KTreeView::raiseItem(KTreeViewItem *item) { KTreeViewItem *itemParent = item->getParent(); int itemChildIndex = itemParent->childIndex(item); if(itemChildIndex > 0) { bool autoU = autoUpdate(); setAutoUpdate(FALSE); takeItem(item); insertItem(itemParent->childAt(--itemChildIndex), item, TRUE); if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } } // find the item at the path KTreeViewItem* KTreeView::recursiveFind(KPath& path) { if (path.isEmpty()) return treeRoot; // get the next key TQString* searchString = path.pop(); // find the parent item KTreeViewItem* parent = recursiveFind(path); if (parent == 0) return 0; /* * Iterate through the parent's children searching for searchString. */ KTreeViewItem* sibling = parent->getChild(); while (sibling != 0) { if (*searchString == sibling->getText()) { break; /* found it! */ } sibling = sibling->getSibling(); } return sibling; } // called by setExpandLevel for each item in tree bool KTreeView::setItemExpanded(KTreeViewItem *item, void *) { if (level(item) < expansion) { if(item->hasChild() && !item->isExpanded()) expandSubTree(item); else item->setExpanded(TRUE); } else { if (item->hasChild() && item->isExpanded()) collapseSubTree(item); else item->setExpanded(FALSE); } return FALSE; } // called by setExpandButtonDrawing for every item in tree bool KTreeView::setItemExpandButtonDrawing(KTreeViewItem *item, void *) { item->setDrawExpandButton(drawExpandButton); return FALSE; } // called by setShowItemText for every item in tree bool KTreeView::setItemShowText(KTreeViewItem *item, void *) { item->setDrawText(showText); return FALSE; } // called by setTreeDrawing for every item in tree bool KTreeView::setItemTreeDrawing(KTreeViewItem *item, void *) { item->setDrawTree(drawTree); return FALSE; } // makes the item a child of the item above it, splitting // the tree into a new branch void KTreeView::split(KTreeViewItem *item) { KTreeViewItem *itemParent = item->getParent(); int itemChildIndex = itemParent->childIndex(item); if(itemChildIndex == 0) return; bool autoU = autoUpdate(); setAutoUpdate(FALSE); takeItem(item); appendChildItem(itemParent->childAt(--itemChildIndex), item); if(autoU && isVisible()) repaint(); setAutoUpdate(autoU); } // removes the item from the tree without deleting it void KTreeView::takeItem(KTreeViewItem* item) { assert(item->owner == this); // TODO: go over this function again bool wasVisible = item->isVisible(); /* * If we have a current item, make sure that it is not in the subtree * that we are about to remove. If the current item is in the part * below the taken-out subtree, we must move it up a number of rows if * the taken-out subtree is at least partially visible. */ KTreeViewItem* cur = current >= 0 ? itemAt(current) : 0; if (wasVisible && cur != 0) { KTreeViewItem* c = cur; while (c != 0 && c != item) { c = c->getParent(); } if (c != 0) { // move current item to parent cur = item->getParent(); if (cur == treeRoot) cur = 0; } } KTreeViewItem* parentItem = item->getParent(); parentItem->removeChild(item); item->sibling = 0; if (wasVisible || parentItem->childCount() == 0) { bool autoU = autoUpdate(); setAutoUpdate(FALSE); updateVisibleItems(); if (autoU && isVisible()) repaint(); setAutoUpdate(autoU); } // re-seat the current item setCurrentItem(cur != 0 ? itemRow(cur) : -1); } // visits each item, calculates the maximum width // and updates TQGridView void KTreeView::updateCellWidth() { // make cells at least 1 pixel wide to avoid singularities (division by zero) int maxW = 1; forEveryVisibleItem(&KTreeView::getMaxItemWidth, &maxW); maxItemWidth = maxW; setCellWidth(maxW); update(); } void KTreeView::updateVisibleItems() { int index = 0; int count = 0; updateVisibleItemRec(treeRoot, index, count); assert(index == count); setNumRows(count); updateCellWidth(); } void KTreeView::updateVisibleItemRec(KTreeViewItem* item, int& index, int& count) { if (!item->isExpanded()) { // no visible items if not expanded return; } /* * Record the children of item in the list of visible items. * * Don't register item itself, it's already in the list. Also only * allocate new space for children. */ count += item->childCount(); if (count > itemCapacity) { // must reallocate int newCapacity = itemCapacity; do { newCapacity += newCapacity; } while (newCapacity < count); KTreeViewItem** newItems = new KTreeViewItem*[newCapacity]; // clear the unneeded space for (int i = index; i < newCapacity; i++) { newItems[i] = 0; } // move already accumulated items over for (int i = index-1; i >= 0; i--) { newItems[i] = visibleItems[i]; } delete[] visibleItems; visibleItems = newItems; itemCapacity = newCapacity; } // insert children for (KTreeViewItem* i = item->getChild(); i != 0; i = i->getSibling()) { visibleItems[index++] = i; updateVisibleItemRec(i, index, count); } }