Diffstat (limited to 'kaddressbook/views/cardview.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r-- | kaddressbook/views/cardview.cpp | 1719 |
1 files changed, 1719 insertions, 0 deletions
diff --git a/kaddressbook/views/cardview.cpp b/kaddressbook/views/cardview.cpp new file mode 100644 index 0000000..65f793c --- a/dev/null +++ b/kaddressbook/views/cardview.cpp @@ -0,0 +1,1719 @@ +/* + This file is part of KAddressBook. + Copyright (c) 2002 Mike Pilone <mpilone@slac.com> + + 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 + MERCHANTABILITY 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + As a special exception, permission is given to link this program + with any edition of Qt, and distribute the resulting executable, + without including the source code for Qt in the source distribution. +*/ + +//BEGIN Includes +#include "cardview.h" + +#include <limits.h> + +#include <qpainter.h> +#include <qtimer.h> +#include <qdatetime.h> +#include <qlabel.h> +#include <qstyle.h> +#include <qcursor.h> +#include <qtooltip.h> + +#include <kdebug.h> +#include <kglobalsettings.h> +//END includes + +#define MIN_ITEM_WIDTH 80 + +//BEGIN Helpers +////////////////////////////////////// +// CardViewTip +class CardViewTip : public QLabel { + public: + CardViewTip(QWidget *parent=0, const char *name=0) : QLabel( parent, name ) + { + setPalette( QToolTip::palette() ); + setFrameStyle( Panel|Plain ); + setMidLineWidth(0); + setIndent(1); + } + + ~CardViewTip() {}; + protected: + void leaveEvent( QEvent * ) + { + hide(); + } +}; + +////////////////////////////////////// +// CardViewItemList + + +// +// Warning: make sure you use findRef() instead of find() to find an +// item! Only the pointer value is unique in the list. +// +class CardViewItemList : public QPtrList<CardViewItem> +{ + protected: + virtual int compareItems(QPtrCollection::Item item1, + QPtrCollection::Item item2) + { + CardViewItem *cItem1 = (CardViewItem*)item1; + CardViewItem *cItem2 = (CardViewItem*)item2; + + if ( cItem1 == cItem2 ) + return 0; + + if ((cItem1 == 0) || (cItem2 == 0)) + return cItem1 ? -1 : 1; + + if (cItem1->caption() < cItem2->caption()) + return -1; + + else if (cItem1->caption() > cItem2->caption()) + return 1; + + return 0; + } + + private: + /*int find( const CardViewItem * ) + { + qDebug("DON'T USE CardViewItemList::find( item )! Use findRef( item )!"); + }*/ +}; + +////////////////////////////////////// +// CardViewSeparator +class CardViewSeparator +{ + friend class CardView; + + public: + CardViewSeparator(CardView *view) + : mView(view) + { + mRect = QRect(0, 0, view->separatorWidth(), 0); + } + + ~CardViewSeparator() {} + + void paintSeparator(QPainter *p, QColorGroup &cg) + { + p->fillRect(0, 0, mRect.width(), mRect.height(), + cg.brush(QColorGroup::Button)); + } + + void repaintSeparator() + { + mView->repaintContents(mRect); + } + + private: + CardView *mView; + QRect mRect; +}; + +//END Helpers + +//BEGIN Private Data + +class CardViewPrivate +{ + public: + CardViewPrivate() + : mSelectionMode( CardView::Multi ), + mDrawCardBorder( true ), + mDrawFieldLabels( true ), + mDrawSeparators( true), + mSepWidth( 2 ), + mShowEmptyFields( false ), + mLayoutDirty( true ), + mLastClickOnItem( false ), + mItemMargin( 0 ), + mItemSpacing( 10 ), + mItemWidth( 200 ), + mMaxFieldLines( INT_MAX ), + mCurrentItem( 0L ), + mLastClickPos( QPoint(0, 0) ), + mRubberBandAnchor( 0 ), + mCompText( QString::null ), + mResizeAnchor(0) + {}; + + CardViewItemList mItemList; + QPtrList<CardViewSeparator> mSeparatorList; + QFontMetrics *mFm; + QFontMetrics *mBFm; // bold font + QFont mHeaderFont; // custom header font + CardView::SelectionMode mSelectionMode; + bool mDrawCardBorder; + bool mDrawFieldLabels; + bool mDrawSeparators; + int mSepWidth; + bool mShowEmptyFields; + bool mLayoutDirty; + bool mLastClickOnItem; + uint mItemMargin; // internal margin in items + uint mItemSpacing; // spacing between items, column seperators and border + int mItemWidth; // width of all items + uint mMaxFieldLines; // Max lines to dispaly pr field + CardViewItem *mCurrentItem; + QPoint mLastClickPos; + QTimer *mTimer; // times out if mouse rests for more than 500 msecs + CardViewTip *mTip; // passed to the item under a resting cursor to display full text + bool mOnSeparator; // set/reset on mouse movement + // for resizing by dragging the separators + int mResizeAnchor; // uint, ulong? the mouse down separator left + int mRubberBandAnchor; // for erasing rubber bands + // data used for resizing. + // as they are beeded by each mouse move while resizing, we store them here, + // saving 8 calculations in each mouse move. + int colspace; // amount of space between items pr column + uint first; // the first col to anchor at for painting rubber bands + int firstX; // X position of first in pixel + int pressed; // the colummn that was pressed on at resizing start + int span; // pressed - first + // key completion + QString mCompText; // current completion string + QDateTime mCompUpdated; // ...was updated at this time +}; + +class CardViewItemPrivate +{ + public: + CardViewItemPrivate() : + x( 0 ), + y( 0 ), + mSelected( false ){}; + + + QString mCaption; + QPtrList< CardViewItem::Field > mFieldList; + bool mSelected; + int x; // horizontal position, set by the view + int y; // vertical position, set by the view + int maxLabelWidth; // the width of the widest label, according to the view font. + int hcache; // height cache +}; +//END Private Data + +//BEGIN CardViewItem + +CardViewItem::CardViewItem(CardView *parent, QString caption) + : d(new CardViewItemPrivate()), mView(parent) +{ + d->mCaption = caption; + + initialize(); +} + +CardViewItem::~CardViewItem() +{ + // Remove ourself from the view + if (mView != 0) + mView->takeItem(this); + + delete d; + d = 0; +} + +void CardViewItem::initialize() +{ + d->mSelected = false; + d->mFieldList.setAutoDelete(true); + d->maxLabelWidth = 0; + d->hcache=0; + + //calcRect(); + + // Add ourself to the view + if (mView != 0) + mView->insertItem(this); +} + +void CardViewItem::paintCard(QPainter *p, QColorGroup &cg) +{ + + if (!mView) + return; + + QPen pen; + QBrush brush; + QFontMetrics fm = *(mView->d->mFm); + QFontMetrics bFm = *(mView->d->mBFm); + bool drawLabels = mView->d->mDrawFieldLabels; + bool drawBorder = mView->d->mDrawCardBorder; + int mg = mView->itemMargin(); + int w = mView->itemWidth() - (mg*2); + int h = height() - (mg*2); + const int colonWidth( fm.width(":") ); + int labelXPos = 2 + mg; + int labelWidth = QMIN( w/2 - 4 - mg, d->maxLabelWidth + colonWidth + 4 ); + int valueXPos = labelWidth + 4 + mg; + int valueWidth = w - labelWidth - 4 - mg; + + p->setFont( mView->font() ); + labelWidth -= colonWidth; // extra space for the colon + + if (!drawLabels) + { + valueXPos = labelXPos; + valueWidth = w - 4; + } + + // Draw a simple box + if (isSelected()) + pen = QPen(cg.highlight(), 1); + else + pen = QPen(cg.button(), 1); + p->setPen(pen); + + // Draw the border - this is only draw if the user asks for it. + if (drawBorder) + p->drawRect( mg, mg, w, h ); + + // set the proper pen color for the caption box + if (isSelected()) + brush = cg.brush(QColorGroup::Highlight); + else + brush = cg.brush(QColorGroup::Button); + + p->fillRect(mg, mg, w, 4 + bFm.height(), brush); + + // Now paint the caption + p->save(); + QFont bFont = mView->headerFont(); + //bFont.setBold(true); + p->setFont(bFont); + if (isSelected()) + p->setPen(cg.highlightedText()); + else + p->setPen(cg.buttonText()); + p->drawText(2+mg, 2+mg + bFm.ascent()/*bFm.height()*//*-bFm.descent()*//*-bFm.leading()*/, trimString(d->mCaption, w-4, bFm)); + p->restore(); + + // Go through the fields and draw them + QPtrListIterator< CardViewItem::Field > iter(d->mFieldList); + QString label, value; + int yPos = mg + 4 + bFm.height()/* + 1*/ + fm.height(); // why the + 1 ??? (anders) + p->setPen(cg.text()); + + int fh = fm.height(); + int cln( 0 ); + QString tmp; + int maxLines = mView->maxFieldLines(); + for (iter.toFirst(); iter.current(); ++iter) + { + value = (*iter)->second; + if ( value.isEmpty() && ! mView->d->mShowEmptyFields ) + continue; + + if (drawLabels) + { + label = trimString((*iter)->first, labelWidth, fm); + p->drawText(labelXPos, yPos, label + ":"); + } +/* US original + for (cln=0; cln <= maxLines; cln++) + { + tmp = value.section('\n',cln,cln); + if ( !tmp.isEmpty() ) p->drawText( valueXPos, yPos + cln*fh, trimString( tmp, valueWidth, fm ) ); + else break; + } +*/ + +//US new implementation + QStringList strlst = QStringList::split('\n', value, true); + + for (cln=0; cln <= maxLines && cln <= (int)strlst.count(); cln++) + { + tmp = strlst[cln]; + + if ( !tmp.isEmpty() ) + p->drawText( valueXPos, yPos + cln*fh, trimString( tmp, valueWidth, fm ) ); + else + break; + + } + + if ( cln == 0 ) cln = 1; + yPos += cln * fh + 2; + } + + // if we are the current item and the view has focus, draw focus rect + if ( mView->currentItem() == this && mView->hasFocus() ) + { +/*US + mView->style().drawPrimitive( QStyle::PE_FocusRect, p, + QRect(0, 0, mView->itemWidth(), h+(2*mg)), cg, + QStyle::Style_FocusAtBorder, + QStyleOption( isSelected() ? cg.highlight() : cg.base() ) ); +*/ + + const QColor pHighl = isSelected() ? cg.highlight() : cg.base(); + const QRect r(0, 0, mView->itemWidth(), h+(2*mg)); +#ifndef DESKTOP_VERSION + mView->style().drawFocusRect(p, r, cg, &pHighl, true); +#endif + } +} + +const QString &CardViewItem::caption() const +{ + return d->mCaption; +} + + +int CardViewItem::height( bool allowCache ) const +{ + // use cache + if ( allowCache && d->hcache ) + return d->hcache; + + // Base height: + // 2 for line width + // 2 for top caption pad + // 2 for bottom caption pad + // 2 pad for the end + // + 2 times the advised margin + int baseHeight = 8 + ( 2 * mView->itemMargin() ); + + // size of font for each field + // 2 pad for each field + + // anders: if the view does not show empty fields, check for value + bool sef = mView->showEmptyFields(); + int fh = mView->d->mFm->height();//lineSpacing(); // font height + //int sp = QMAX( 0, 2- mView->d->mFm->leading() ); // field spacing NOTE make a property + int fieldHeight = 0; + int lines; + int maxLines( mView->maxFieldLines() ); + QPtrListIterator< CardViewItem::Field > iter(d->mFieldList); + for (iter.toFirst(); iter.current(); ++iter) + { + if ( !sef && (*iter)->second.isEmpty() ) + continue; + lines = QMIN( (*iter)->second.contains('\n') + 1, maxLines ); + fieldHeight += ( lines * fh ) + 2;//sp; + } + + // height of caption font (bold) + fieldHeight += mView->d->mBFm->height(); + d->hcache = baseHeight + fieldHeight; + return d->hcache; +} + +bool CardViewItem::isSelected() const +{ + return d->mSelected; +} + +void CardViewItem::setSelected(bool selected) +{ + d->mSelected = selected; +} + +void CardViewItem::insertField(const QString &label, const QString &value) +{ + CardViewItem::Field *f = new CardViewItem::Field(label, value); + d->mFieldList.append(f); + d->hcache=0; + + if (mView) + { + mView->setLayoutDirty(true); + d->maxLabelWidth = QMAX( mView->d->mFm->width( label ), d->maxLabelWidth ); + } +} + +void CardViewItem::removeField(const QString &label) +{ + CardViewItem::Field *f; + + QPtrListIterator< CardViewItem::Field > iter(d->mFieldList); + for (iter.toFirst(); iter.current(); ++iter) + { + f = *iter; + if (f->first == label) + break; + } + + if (*iter) + d->mFieldList.remove(*iter); + d->hcache = 0; + + if (mView) + mView->setLayoutDirty(true); +} + +void CardViewItem::clearFields() +{ + d->mFieldList.clear(); + d->hcache = 0; + + if (mView) + mView->setLayoutDirty(true); +} + +QString CardViewItem::trimString(const QString &text, int width, + QFontMetrics &fm) +{ + if (fm.width(text) <= width) + return text; + + QString dots = "..."; + int dotWidth = fm.width(dots); + QString trimmed; + int charNum = 0; + + while (fm.width(trimmed) + dotWidth < width) + { + trimmed += text[charNum]; + charNum++; + } + + // Now trim the last char, since it put the width over the top + trimmed = trimmed.left(trimmed.length()-1); + trimmed += dots; + + return trimmed; +} + +CardViewItem *CardViewItem::nextItem() +{ + CardViewItem *item = 0; + + if (mView) + item = mView->itemAfter(this); + + return item; +} + +void CardViewItem::repaintCard() +{ + if (mView) + mView->repaintItem(this); +} + +void CardViewItem::setCaption(const QString &caption) +{ + d->mCaption = caption; + repaintCard(); +} + +QString CardViewItem::fieldValue(const QString &label) +{ + QPtrListIterator< CardViewItem::Field > iter(d->mFieldList); + for (iter.toFirst(); iter.current(); ++iter) + if ((*iter)->first == label) + return (*iter)->second; + + return QString(); +} + + +void CardViewItem::showFullString( const QPoint &itempos, CardViewTip *tip ) +{ + bool trimmed( false ); + QString s; + int mrg = mView->itemMargin(); + int y = mView->d->mBFm->height() + 6 + mrg; + int w = mView->itemWidth() - (2*mrg); + int lw; + bool drawLabels = mView->drawFieldLabels(); + bool isLabel = drawLabels && itempos.x() < w/2 ? true : false; + + if ( itempos.y() < y ) + { + if ( itempos.y() < 8 + mrg || itempos.y() > y - 4 ) + return; + // this is the caption + s = caption(); + trimmed = mView->d->mBFm->width( s ) > w - 4; + y = 2 + mrg; + lw = 0; + isLabel=true; + } else { + // find the field + Field *f = fieldAt( itempos ); + if ( !f || ( !mView->showEmptyFields() && f->second.isEmpty() ) ) + return; + + // y position: + // header font height + 4px hader margin + 2px leading + item margin + // + actual field index * (fontheight + 2px leading) + int maxLines = mView->maxFieldLines(); + bool se = mView->showEmptyFields(); + int fh = mView->d->mFm->height(); +// { + Field *_f; + for (_f = d->mFieldList.first(); _f != f; _f = d->mFieldList.next()) + if ( se || ! _f->second.isEmpty() ) + y += ( QMIN(_f->second.contains('\n')+1, maxLines) * fh ) + 2; +// } + if ( isLabel && itempos.y() > y + fh ) + return; + // label or data? + s = isLabel ? f->first : f->second; + // trimmed? + int colonWidth = mView->d->mFm->width(":"); + lw = drawLabels ? // label width + QMIN( w/2 - 4 - mrg, d->maxLabelWidth + colonWidth + 4 ) : + 0; + int mw = isLabel ? lw - colonWidth : w - lw - (mrg*2); // max width for string + if ( isLabel ) + { + trimmed = mView->d->mFm->width( s ) > mw - colonWidth; + } else { + QRect r( mView->d->mFm->boundingRect( 0, 0, INT_MAX, INT_MAX, Qt::AlignTop|Qt::AlignLeft, s ) ); + trimmed = r.width() > mw || r.height()/fh > QMIN(s.contains('\n') + 1, maxLines); + } + } + if ( trimmed ) + { + tip->setFont( (isLabel && !lw) ? mView->headerFont() : mView->font() ); // if condition is true, a header + tip->setText( s ); + tip->adjustSize(); + // find a proper position + int lx; + lx = isLabel || !drawLabels ? mrg : lw + mrg + 2 /*-1*/; + QPoint pnt(mView->contentsToViewport( QPoint(d->x, d->y) )); + pnt += QPoint(lx, y); + if ( pnt.x() < 0 ) + pnt.setX( 0 ); + if ( pnt.x() + tip->width() > mView->visibleWidth() ) + pnt.setX( mView->visibleWidth() - tip->width() ); + if ( pnt.y() + tip->height() > mView->visibleHeight() ) + pnt.setY( QMAX( 0, mView->visibleHeight() - tip->height() ) ); + // show + tip->move( pnt ); + tip->show(); + } +} + +CardViewItem::Field *CardViewItem::fieldAt( const QPoint & itempos ) const +{ + int ypos = mView->d->mBFm->height() + 7 + mView->d->mItemMargin; + int iy = itempos.y(); + // skip below caption + if ( iy <= ypos ) + return 0; + // try find a field + bool showEmpty = mView->showEmptyFields(); + int fh = mView->d->mFm->height(); + int maxLines = mView->maxFieldLines(); + Field *f; + for ( f = d->mFieldList.first(); f; f = d->mFieldList.next() ) + { + if ( showEmpty || !f->second.isEmpty() ) + ypos += ( QMIN( f->second.contains('\n')+1, maxLines ) *fh)+2; + if ( iy <= ypos ) + break; + } + return f ? f : 0; +} +//END CardViewItem + +//BEGIN CardView + +CardView::CardView(QWidget *parent, const char *name) + : QScrollView(parent, name), + d(new CardViewPrivate()) +{ + d->mItemList.setAutoDelete(true); + d->mSeparatorList.setAutoDelete(true); + + QFont f = font(); + d->mFm = new QFontMetrics(f); + f.setBold(true); + d->mHeaderFont = f; + d->mBFm = new QFontMetrics(f); + d->mTip = ( new CardViewTip( viewport() ) ), + d->mTip->hide(); + d->mTimer = ( new QTimer(this, "mouseTimer") ), + + viewport()->setMouseTracking( true ); + viewport()->setFocusProxy(this); + viewport()->setFocusPolicy(WheelFocus); + viewport()->setBackgroundMode(PaletteBase); + + connect( d->mTimer, SIGNAL(timeout()), this, SLOT(tryShowFullText()) ); + +//US setBackgroundMode(PaletteBackground, PaletteBase); + setBackgroundMode(PaletteBackground); + + // no reason for a vertical scrollbar + setVScrollBarMode(AlwaysOff); +} + +CardView::~CardView() +{ + delete d->mFm; + delete d->mBFm; + delete d; + d = 0; +} + +void CardView::insertItem(CardViewItem *item) +{ + d->mItemList.inSort(item); + setLayoutDirty(true); +} + +void CardView::takeItem(CardViewItem *item) +{ + if ( d->mCurrentItem == item ) + d->mCurrentItem = item->nextItem(); + d->mItemList.take(d->mItemList.findRef(item)); + + setLayoutDirty(true); +} + +void CardView::clear() +{ + d->mItemList.clear(); + + setLayoutDirty(true); +} + +CardViewItem *CardView::currentItem() +{ + if ( ! d->mCurrentItem && d->mItemList.count() ) + d->mCurrentItem = d->mItemList.first(); + return d->mCurrentItem; +} + +void CardView::setCurrentItem( CardViewItem *item ) +{ + if ( !item ) + return; + else if ( item->cardView() != this ) + { + kdDebug(5720)<<"CardView::setCurrentItem: Item ("<<item<<") not owned! Backing out.."<<endl; + return; + } + else if ( item == currentItem() ) + { + return; + } + + if ( d->mSelectionMode == Single ) + { + setSelected( item, true ); + } + else + { + CardViewItem *it = d->mCurrentItem; + d->mCurrentItem = item; + if ( it ) + it->repaintCard(); + item->repaintCard(); + } + if ( ! d->mOnSeparator ) + ensureItemVisible( item ); + emit currentChanged( item ); +} + +CardViewItem *CardView::itemAt(const QPoint &viewPos) +{ + CardViewItem *item = 0; + QPtrListIterator<CardViewItem> iter(d->mItemList); + bool found = false; + for (iter.toFirst(); iter.current() && !found; ++iter) + { + item = *iter; + //if (item->d->mRect.contains(viewPos)) + if (QRect(item->d->x, item->d->y, d->mItemWidth, item->height()).contains(viewPos)) + found = true; + } + + if (found) + return item; + + return 0; +} + +QRect CardView::itemRect(const CardViewItem *item) +{ + //return item->d->mRect; + return QRect(item->d->x, item->d->y, d->mItemWidth, item->height()); +} + +void CardView::ensureItemVisible(const CardViewItem *item) +{ + ensureVisible(item->d->x , item->d->y, d->mItemSpacing, 0); + ensureVisible(item->d->x + d->mItemWidth, item->d->y, d->mItemSpacing, 0); +} + +void CardView::repaintItem(const CardViewItem *item) +{ + //repaintContents(item->d->mRect); + repaintContents( QRect(item->d->x, item->d->y, d->mItemWidth, item->height()) ); +} + +void CardView::setSelectionMode(CardView::SelectionMode mode) +{ + selectAll(false); + + d->mSelectionMode = mode; +} + +CardView::SelectionMode CardView::selectionMode() const +{ + return d->mSelectionMode; +} + +void CardView::selectAll(bool state) +{ + QPtrListIterator<CardViewItem> iter(d->mItemList); + if (!state) + { + for (iter.toFirst(); iter.current(); ++iter) + { + if ((*iter)->isSelected()) + { + (*iter)->setSelected(false); + (*iter)->repaintCard(); + } + } + //emit selectionChanged(); // WARNING FIXME + emit selectionChanged(0); + } + else if (d->mSelectionMode != CardView::Single) + { + for (iter.toFirst(); iter.current(); ++iter) + { + (*iter)->setSelected(true); + } + + if (d->mItemList.count() > 0) + { + // emit, since there must have been at least one selected + emit selectionChanged(); + //repaint();//??? + viewport()->update(); + } + } +} + +void CardView::setSelected(CardViewItem *item, bool selected) +{ + if ((item == 0) || (item->isSelected() == selected)) + return; + + if ( selected && d->mCurrentItem != item ) + { + CardViewItem *it = d->mCurrentItem; + d->mCurrentItem = item; + if ( it ) + it->repaintCard(); + } + + if (d->mSelectionMode == CardView::Single) + { + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + + if (selected) + { + item->setSelected(selected); + item->repaintCard(); + emit selectionChanged(); + emit selectionChanged(item); + } + else + { + emit selectionChanged(); + emit selectionChanged(0); + } + } + else if (d->mSelectionMode == CardView::Multi) + { + item->setSelected(selected); + item->repaintCard(); + emit selectionChanged(); + } + else if (d->mSelectionMode == CardView::Extended) + { + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + + item->setSelected(selected); + item->repaintCard(); + emit selectionChanged(); + } +} + +bool CardView::isSelected(CardViewItem *item) const +{ + return (item && item->isSelected()); +} + +CardViewItem *CardView::selectedItem() const +{ + // find the first selected item + QPtrListIterator<CardViewItem> iter(d->mItemList); + for (iter.toFirst(); iter.current(); ++iter) + { + if ((*iter)->isSelected()) + return *iter; + } + + return 0; +} + +CardViewItem *CardView::firstItem() const +{ + return d->mItemList.first(); +} + +int CardView::childCount() const +{ + return d->mItemList.count(); +} +/*US +CardViewItem *CardView::findItem(const QString &text, const QString &label, + Qt::StringComparisonMode compare) +{ + // IF the text is empty, we will return null, since empty text will + // match anything! + if (text.isEmpty()) + return 0; + + QPtrListIterator<CardViewItem> iter(d->mItemList); + if (compare & Qt::BeginsWith) + { + QString value; + for (iter.toFirst(); iter.current(); ++iter) + { + value = (*iter)->fieldValue(label).upper(); + if (value.startsWith(text.upper())) + return *iter; + } + } + else + { + kdDebug(5720) << "CardView::findItem: search method not implemented" << endl; + } + + return 0; +} +*/ + +uint CardView::columnWidth() +{ + return d->mDrawSeparators ? + d->mItemWidth + ( 2 * d->mItemSpacing ) + d->mSepWidth : + d->mItemWidth + d->mItemSpacing; +} + +void CardView::drawContents(QPainter *p, int clipx, int clipy, + int clipw, int cliph) +{ + QScrollView::drawContents(p, clipx, clipy, clipw, cliph); + + if (d->mLayoutDirty) + calcLayout(); + + //kdDebug() << "CardView::drawContents: " << clipx << ", " << clipy + // << ", " << clipw << ", " << cliph << endl; + + QColorGroup cg = viewport()->palette().active(); // allow setting costum colors in the viewport pale + + QRect clipRect(clipx, clipy, clipw, cliph); + QRect cardRect; + QRect sepRect; + CardViewItem *item; + CardViewSeparator *sep; + + // make sure the viewport is a pure background + viewport()->erase(clipRect); + + // Now tell the cards to draw, if they are in the clip region + QPtrListIterator<CardViewItem> iter(d->mItemList); + for (iter.toFirst(); iter.current(); ++iter) + { + item = *iter; + cardRect.setRect( item->d->x, item->d->y, d->mItemWidth, item->height() ); + + if (clipRect.intersects(cardRect) || clipRect.contains(cardRect)) + { + //kdDebug() << "\trepainting card at: " << cardRect.x() << ", " + // << cardRect.y() << endl; + + // Tell the card to paint + p->save(); + p->translate(cardRect.x(), cardRect.y()); + item->paintCard(p, cg); + p->restore(); + } + } + + // Followed by the separators if they are in the clip region + QPtrListIterator<CardViewSeparator> sepIter(d->mSeparatorList); + for (sepIter.toFirst(); sepIter.current(); ++sepIter) + { + sep = *sepIter; + sepRect = sep->mRect; + + if (clipRect.intersects(sepRect) || clipRect.contains(sepRect)) + { + p->save(); + p->translate(sepRect.x(), sepRect.y()); + sep->paintSeparator(p, cg); + p->restore(); + } + } +} + +void CardView::resizeEvent(QResizeEvent *e) +{ + QScrollView::resizeEvent(e); + + setLayoutDirty(true); +} + +void CardView::calcLayout() +{ + //kdDebug() << "CardView::calcLayout:" << endl; + + // Start in the upper left corner and layout all the + // cars using their height and width + int maxWidth = 0; + int maxHeight = 0; + int xPos = 0; + int yPos = 0; + int cardSpacing = d->mItemSpacing; + + // delete the old separators + d->mSeparatorList.clear(); + + QPtrListIterator<CardViewItem> iter(d->mItemList); + CardViewItem *item = 0; + CardViewSeparator *sep = 0; + xPos += cardSpacing; + + for (iter.toFirst(); iter.current(); ++iter) + { + item = *iter; + + yPos += cardSpacing; + + if (yPos + item->height() + cardSpacing >= height() - horizontalScrollBar()->height()) + { + maxHeight = QMAX(maxHeight, yPos); + + // Drawing in this column would be greater than the height + // of the scroll view, so move to next column + yPos = cardSpacing; + xPos += cardSpacing + maxWidth; + if (d->mDrawSeparators) + { + // Create a separator since the user asked + sep = new CardViewSeparator(this); + sep->mRect.moveTopLeft(QPoint(xPos, yPos+d->mItemMargin)); + xPos += d->mSepWidth + cardSpacing; + d->mSeparatorList.append(sep); + } + + maxWidth = 0; + } + + item->d->x = xPos; + item->d->y = yPos; + + yPos += item->height(); + maxWidth = QMAX(maxWidth, d->mItemWidth); + } + + xPos += maxWidth; + resizeContents( xPos + cardSpacing, maxHeight ); + + // Update the height of all the separators now that we know the + // max height of a column + QPtrListIterator<CardViewSeparator> sepIter(d->mSeparatorList); + for (sepIter.toFirst(); sepIter.current(); ++sepIter) + { + (*sepIter)->mRect.setHeight(maxHeight - 2*cardSpacing - 2*d->mItemMargin); + } + + d->mLayoutDirty = false; +} + +CardViewItem *CardView::itemAfter(CardViewItem *item) +{ + /*int pos = */d->mItemList.findRef(item); + return d->mItemList.next();//at(pos+1); +} + +uint CardView::itemMargin() +{ + return d->mItemMargin; +} + +void CardView::setItemMargin( uint margin ) +{ + if ( margin == d->mItemMargin ) + return; + + d->mItemMargin = margin; + setLayoutDirty( true ); +} + +uint CardView::itemSpacing() +{ + return d->mItemSpacing; +} + +void CardView::setItemSpacing( uint spacing ) +{ + if ( spacing == d->mItemSpacing ) + return; + + d->mItemSpacing = spacing; + setLayoutDirty( true ); +} + +void CardView::contentsMousePressEvent(QMouseEvent *e) +{ + QScrollView::contentsMousePressEvent(e); + + QPoint pos = e->pos(); + d->mLastClickPos = pos; + + CardViewItem *item = itemAt(pos); + + if (item == 0) + { + d->mLastClickOnItem = false; + if ( d->mOnSeparator) + { + d->mResizeAnchor = e->x()+contentsX(); + d->colspace = (2*d->mItemSpacing) /*+ (2*d->mItemMargin)*/; + int ccw = d->mItemWidth + d->colspace + d->mSepWidth; + d->first = (contentsX()+d->mSepWidth)/ccw; + d->pressed = (d->mResizeAnchor+d->mSepWidth)/ccw; + d->span = d->pressed - d->first; + d->firstX = d->first * ccw; + if ( d->firstX ) d->firstX -= d->mSepWidth; // (no sep in col 0) + } + else + { + selectAll(false); + } + return; + } + + d->mLastClickOnItem = true; + + CardViewItem *other = d->mCurrentItem; + setCurrentItem( item ); + + // Always emit the selection + emit clicked(item); + + // Check the selection type and update accordingly + if (d->mSelectionMode == CardView::Single) + { + // make sure it isn't already selected + if (item->isSelected()) + return; + + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + + item->setSelected(true); + item->repaintCard(); + emit selectionChanged(item); + } + + else if (d->mSelectionMode == CardView::Multi) + { + // toggle the selection + item->setSelected(!item->isSelected()); + item->repaintCard(); + emit selectionChanged(); + } + + else if (d->mSelectionMode == CardView::Extended) + { + if ((e->button() & Qt::LeftButton) && + (e->state() & Qt::ShiftButton)) + { + if ( item == other ) return; + + bool s = ! item->isSelected(); + + if ( s && ! (e->state() & ControlButton) ) + { + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + } + + int from, to, a, b; + a = d->mItemList.findRef( item ); + b = d->mItemList.findRef( other ); + from = a < b ? a : b; + to = a > b ? a : b; + //kdDebug()<<"selecting items "<<from<<" - "<<to<<" ( "<<s<<" )"<<endl; + CardViewItem *aItem; + for ( ; from <= to; from++ ) + { + aItem = d->mItemList.at( from ); + aItem->setSelected( s ); + repaintItem( aItem ); + } + emit selectionChanged(); + } + else if ((e->button() & Qt::LeftButton) && + (e->state() & Qt::ControlButton)) + { + item->setSelected(!item->isSelected()); + item->repaintCard(); + emit selectionChanged(); + } + + else if (e->button() & Qt::LeftButton) + { + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + + item->setSelected(true); + item->repaintCard(); + emit selectionChanged(); + } + } + +} + +void CardView::contentsMouseReleaseEvent(QMouseEvent *e) +{ + QScrollView::contentsMouseReleaseEvent(e); + + if ( d->mResizeAnchor ) + { + // finish the resizing: + unsetCursor(); + // hide rubber bands + int newiw = d->mItemWidth - ((d->mResizeAnchor - d->mRubberBandAnchor)/d->span); + drawRubberBands( 0 ); + // we should move to reflect the new position if we are scrolled. + if ( contentsX() ) + { + int newX = QMAX( 0, ( d->pressed * ( newiw + d->colspace + d->mSepWidth ) ) - e->x() ); + setContentsPos( newX, contentsY() ); + } + // set new item width + setItemWidth( newiw ); + // reset anchors + d->mResizeAnchor = 0; + d->mRubberBandAnchor = 0; + return; + } + + // If there are accel keys, we will not emit signals + if ((e->state() & Qt::ShiftButton) || (e->state() & Qt::ControlButton)) + return; + + // Get the item at this position + CardViewItem *item = itemAt(e->pos()); + + if (item && KGlobalSettings::singleClick()) + { + emit executed(item); + } +} + +void CardView::contentsMouseDoubleClickEvent(QMouseEvent *e) +{ + QScrollView::contentsMouseDoubleClickEvent(e); + + CardViewItem *item = itemAt(e->pos()); + + if (item) + { + d->mCurrentItem = item; + } + + if (item && !KGlobalSettings::singleClick()) + { + emit executed(item); + } + emit doubleClicked(item); +} + +void CardView::contentsMouseMoveEvent( QMouseEvent *e ) +{ + // resizing + if ( d->mResizeAnchor ) + { + int x = e->x(); + if ( x != d->mRubberBandAnchor ) + drawRubberBands( x ); + return; + } + + if (d->mLastClickOnItem && (e->state() & Qt::LeftButton) && + ((e->pos() - d->mLastClickPos).manhattanLength() > 4)) { + + startDrag(); + return; + } + + d->mTimer->start( 500 ); + + // see if we are over a separator + // only if we actually have them painted? + if ( d->mDrawSeparators ) + { + int colcontentw = d->mItemWidth + (2*d->mItemSpacing); + int colw = colcontentw + d->mSepWidth; + int m = e->x()%colw; + if ( m >= colcontentw && m > 0 ) + { + setCursor( SplitVCursor ); // Why does this fail sometimes? + d->mOnSeparator = true; + } + else + { + setCursor( ArrowCursor ); + d->mOnSeparator = false; + } + } +} + +void CardView::enterEvent( QEvent * ) +{ + d->mTimer->start( 500 ); +} + +void CardView::leaveEvent( QEvent * ) +{ + d->mTimer->stop(); + if (d->mOnSeparator) + { + d->mOnSeparator = false; + setCursor( ArrowCursor ); + } +} + +void CardView::focusInEvent( QFocusEvent * ) +{ + if (!d->mCurrentItem && d->mItemList.count() ) + { + setCurrentItem( d->mItemList.first() ); + } + else if ( d->mCurrentItem ) + { + d->mCurrentItem->repaintCard(); + } +} + +void CardView::focusOutEvent( QFocusEvent * ) +{ + if (d->mCurrentItem) + d->mCurrentItem->repaintCard(); +} + +void CardView::keyPressEvent( QKeyEvent *e ) +{ + if ( ! ( childCount() && d->mCurrentItem ) ) + { + e->ignore(); + return; + } + + uint pos = d->mItemList.findRef( d->mCurrentItem ); + CardViewItem *aItem = 0L; // item that gets the focus + CardViewItem *old = d->mCurrentItem; + + switch ( e->key() ) + { + case Key_Up: + if ( pos > 0 ) + { + aItem = d->mItemList.at( pos - 1 ); + setCurrentItem( aItem ); + } + break; + case Key_Down: + if ( pos < d->mItemList.count() - 1 ) + { + aItem = d->mItemList.at( pos + 1 ); + setCurrentItem( aItem ); + } + break; + case Key_Left: + { + // look for an item in the previous/next column, starting from + // the vertical middle of the current item. + // FIXME use nice calculatd measures!!! + QPoint aPoint( d->mCurrentItem->d->x, d->mCurrentItem->d->y ); + aPoint -= QPoint( 30,-(d->mCurrentItem->height()/2) ); + aItem = itemAt( aPoint ); + // maybe we hit some space below an item + while ( !aItem && aPoint.y() > 27 ) + { + aPoint -= QPoint( 0, 16 ); + aItem = itemAt( aPoint ); + } + if ( aItem ) + setCurrentItem( aItem ); + } + break; + case Key_Right: + { + // FIXME use nice calculated measures!!! + QPoint aPoint( d->mCurrentItem->d->x + d->mItemWidth, d->mCurrentItem->d->y ); + aPoint += QPoint( 30,(d->mCurrentItem->height()/2) ); + aItem = itemAt( aPoint ); + while ( !aItem && aPoint.y() > 27 ) + { + aPoint -= QPoint( 0, 16 ); + aItem = itemAt( aPoint ); + } + if ( aItem ) + setCurrentItem( aItem ); + } + break; + case Key_Home: + aItem = d->mItemList.first(); + setCurrentItem( aItem ); + break; + case Key_End: + aItem = d->mItemList.last(); + setCurrentItem( aItem ); + break; + case Key_Prior: // PageUp + { + // QListView: "Make the item above the top visible and current" + // TODO if contentsY(), pick the top item of the leftmost visible column + if ( contentsX() <= 0 ) + return; + int cw = columnWidth(); + int theCol = ( QMAX( 0, ( contentsX()/cw) * cw ) ) + d->mItemSpacing; + aItem = itemAt( QPoint( theCol + 1, d->mItemSpacing + 1 ) ); + if ( aItem ) + setCurrentItem( aItem ); + } + break; + case Key_Next: // PageDown + { + // QListView: "Make the item below the bottom visible and current" + // find the first not fully visible column. + // TODO: consider if a partly visible (or even hidden) item at the + // bottom of the rightmost column exists + int cw = columnWidth(); + int theCol = ( (( contentsX() + visibleWidth() )/cw) * cw ) + d->mItemSpacing + 1; + // if separators are on, we may need to we may be one column further right if only the spacing/sep is hidden + if ( d->mDrawSeparators && cw - (( contentsX() + visibleWidth() )%cw) <= int( d->mItemSpacing + d->mSepWidth ) ) + theCol += cw; + + // make sure this is not too far right + while ( theCol > contentsWidth() ) + theCol -= columnWidth(); + + aItem = itemAt( QPoint( theCol, d->mItemSpacing + 1 ) ); + + if ( aItem ) + setCurrentItem( aItem ); + } + break; + case Key_Space: + setSelected( d->mCurrentItem, !d->mCurrentItem->isSelected() ); + emit selectionChanged(); + break; + case Key_Return: + case Key_Enter: + emit returnPressed( d->mCurrentItem ); + emit executed( d->mCurrentItem ); + break; + default: + if ( (e->state() & ControlButton) && e->key() == Key_A ) + { + // select all + selectAll( true ); + break; + } + // if we have a string, do autosearch + else if ( ! e->text().isEmpty() && e->text()[0].isPrint() ) + { + + } + break; + } + // handle selection + if ( aItem ) + { + if ( d->mSelectionMode == CardView::Extended ) + { + if ( (e->state() & ShiftButton) ) + { + // shift button: toggle range + // if control button is pressed, leave all items + // and toggle selection current->old current + // otherwise, ?????? + bool s = ! aItem->isSelected(); + int from, to, a, b; + a = d->mItemList.findRef( aItem ); + b = d->mItemList.findRef( old ); + from = a < b ? a : b; + to = a > b ? a : b; + + if ( to - from > 1 ) + { + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + } + + //kdDebug()<<"selecting items "<<from<<" - "<<to<<" ( "<<s<<" )"<<endl; + CardViewItem *item; + for ( ; from <= to; from++ ) + { + item = d->mItemList.at( from ); + item->setSelected( s ); + repaintItem( item ); + } + emit selectionChanged(); + } + else if ( (e->state() & ControlButton) ) + { + // control button: do nothing + } + else + { + // no button: move selection to this item + bool b = signalsBlocked(); + blockSignals(true); + selectAll(false); + blockSignals(b); + + setSelected( aItem, true ); + emit selectionChanged(); + } + } + } +} + +void CardView::contentsWheelEvent( QWheelEvent * e ) +{ + scrollBy(2*e->delta()/-3, 0); +} + +void CardView::setLayoutDirty(bool dirty) +{ + if (d->mLayoutDirty != dirty) + { + d->mLayoutDirty = dirty; + repaint(); + } +} + +void CardView::setDrawCardBorder(bool enabled) +{ + if (enabled != d->mDrawCardBorder) + { + d->mDrawCardBorder = enabled; + repaint(); + } +} + +bool CardView::drawCardBorder() const +{ + return d->mDrawCardBorder; +} + +void CardView::setDrawColSeparators(bool enabled) +{ + if (enabled != d->mDrawSeparators) + { + d->mDrawSeparators = enabled; + setLayoutDirty(true); + } +} + +bool CardView::drawColSeparators() const +{ + return d->mDrawSeparators; +} + +void CardView::setDrawFieldLabels(bool enabled) +{ + if (enabled != d->mDrawFieldLabels) + { + d->mDrawFieldLabels = enabled; + repaint(); + } +} + +bool CardView::drawFieldLabels() const +{ + return d->mDrawFieldLabels; +} + +void CardView::setShowEmptyFields(bool show) +{ + if (show != d->mShowEmptyFields) + { + d->mShowEmptyFields = show; + setLayoutDirty(true); + } +} + +bool CardView::showEmptyFields() const +{ + return d->mShowEmptyFields; +} + +void CardView::startDrag() +{ + // The default implementation is a no-op. It must be + // reimplemented in a subclass to be useful +} +void CardView::tryShowFullText() +{ + d->mTimer->stop(); + // if we have an item + QPoint cpos = viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ); + CardViewItem *item = itemAt( cpos ); + if ( item ) + { + // query it for a value to display + //QString s = item ? item->caption() : "(no item)"; + //kdDebug()<<"MOUSE REST: "<<s<<endl; + QPoint ipos = cpos - itemRect( item ).topLeft(); + item->showFullString( ipos, d->mTip ); + } +} + +void CardView::drawRubberBands( int pos ) +{ + if ( pos && ((pos-d->firstX)/d->span) - d->colspace - d->mSepWidth < MIN_ITEM_WIDTH ) return; + + int tmpcw = (d->mRubberBandAnchor-d->firstX)/d->span; + int x = d->firstX + tmpcw - d->mSepWidth - contentsX(); + int h = visibleHeight(); + + QPainter p( viewport() ); + p.setRasterOp( XorROP ); + p.setPen( gray ); + p.setBrush( gray ); + uint n = d->first; + // erase + if ( d->mRubberBandAnchor ) + do { + p.drawRect( x, 0, 2, h ); + x += tmpcw; + n++; + } while ( x < visibleWidth() && n < d->mSeparatorList.count() ); + // paint new + if ( ! pos ) return; + tmpcw = (pos - d->firstX)/d->span; + n = d->first; + x = d->firstX + tmpcw - d->mSepWidth - contentsX(); + do { + p.drawRect( x, 0, 2, h ); + x += tmpcw; + n++; + } while ( x < visibleWidth() && n < d->mSeparatorList.count() ); + d->mRubberBandAnchor = pos; +} + + +int CardView::itemWidth() const +{ + return d->mItemWidth; +} + +void CardView::setItemWidth( int w ) +{ + if ( w == d->mItemWidth ) + return; + if ( w < MIN_ITEM_WIDTH ) + w = MIN_ITEM_WIDTH; + d->mItemWidth = w; + setLayoutDirty( true ); +#ifndef KAB_EMBEDDED + updateContents(); +#else //KAB_EMBEDDED +//US updateContents( d->contentsX(), d->contentsY(), visibleWidth(), visibleHeight() ); +qDebug("CardView::setItemWidth has to be verified"); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); +#endif //KAB_EMBEDDED +} + +void CardView::setHeaderFont( const QFont &fnt ) +{ + d->mHeaderFont = fnt; + delete d->mBFm; + d->mBFm = new QFontMetrics( fnt ); +} + +QFont CardView::headerFont() const +{ + return d->mHeaderFont; +} + +void CardView::setFont( const QFont &fnt ) +{ + QScrollView::setFont( fnt ); + delete d->mFm; + d->mFm = new QFontMetrics( fnt ); +} + +int CardView::separatorWidth() +{ + return d->mSepWidth; +} + +void CardView::setSeparatorWidth( int width ) +{ + d->mSepWidth = width; + setLayoutDirty( true ); // hmm, actually I could just adjust the x'es... +} + +int CardView::maxFieldLines() const +{ + return d->mMaxFieldLines; +} + +void CardView::setMaxFieldLines( int howmany ) +{ + d->mMaxFieldLines = howmany ? howmany : INT_MAX; + // FIXME update, forcing the items to recalc height!! +} +//END Cardview + +#ifndef KAB_EMBEDDED +#include "cardview.moc" +#endif //KAB_EMBEDDED |