/*************************************************************************** register.cpp - description ------------------- begin : Fri Mar 10 2006 copyright : (C) 2006 by Thomas Baumgart email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include #include #include #include #include "scheduledtransaction.h" #include "../kmymoneyglobalsettings.h" const int LinesPerMemo = 3; static TQString sortOrderText[] = { I18N_NOOP("Unknown"), I18N_NOOP("Post date"), I18N_NOOP("Date entered"), I18N_NOOP("Payee"), I18N_NOOP("Amount"), I18N_NOOP("Number"), I18N_NOOP("Entry order"), I18N_NOOP("Type"), I18N_NOOP("Category"), I18N_NOOP("Reconcile state"), I18N_NOOP("Security") // add new values above this comment line }; using namespace KMyMoneyRegister; static unsigned char fancymarker_bg_image[] = { /* 200x49 */ 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A, 0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52, 0x00,0x00,0x00,0xC8,0x00,0x00,0x00,0x31, 0x08,0x06,0x00,0x00,0x00,0x9F,0xC5,0xE6, 0x4F,0x00,0x00,0x00,0x06,0x62,0x4B,0x47, 0x44,0x00,0xFF,0x00,0xFF,0x00,0xFF,0xA0, 0xBD,0xA7,0x93,0x00,0x00,0x00,0x09,0x70, 0x48,0x59,0x73,0x00,0x00,0x0B,0x13,0x00, 0x00,0x0B,0x13,0x01,0x00,0x9A,0x9C,0x18, 0x00,0x00,0x00,0x86,0x49,0x44,0x41,0x54, 0x78,0xDA,0xED,0xDD,0x31,0x0A,0x84,0x40, 0x10,0x44,0xD1,0x1A,0x19,0x10,0xCF,0xE6, 0xFD,0x4F,0xB2,0x88,0x08,0x22,0x9B,0x18, 0x4E,0x1B,0x2C,0x1B,0x18,0xBC,0x07,0x7D, 0x81,0x82,0x1F,0x77,0x4B,0xB2,0x06,0x18, 0xEA,0x49,0x3E,0x66,0x00,0x81,0x80,0x40, 0xE0,0xDF,0x81,0x6C,0x66,0x80,0x3A,0x90, 0xDD,0x0C,0x50,0x07,0x72,0x98,0x01,0xEA, 0x40,0x4E,0x33,0x40,0x1D,0xC8,0x65,0x06, 0x18,0x6B,0xF7,0x01,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0xF0,0x16,0x3E, 0x4C,0xC1,0x83,0x9E,0x64,0x32,0x03,0x08, 0x04,0x7E,0x0A,0xA4,0x9B,0x01,0xEA,0x40, 0x66,0x33,0x40,0x1D,0xC8,0x62,0x06,0x18, 0xFB,0x02,0x05,0x87,0x08,0x55,0xFE,0xDE, 0xA2,0x9D,0x00,0x00,0x00,0x00,0x49,0x45, 0x4E,0x44,0xAE,0x42,0x60,0x82 }; TQPixmap* GroupMarker::m_bg = 0; int GroupMarker::m_bgRefCnt = 0; void ItemPtrVector::sort(void) { if(count() > 0) { // get rid of 0 pointers in the list KMyMoneyRegister::ItemPtrVector::iterator it_l; RegisterItem *item; for(it_l = begin(); it_l != end(); ++it_l) { if(*it_l == 0) { item = last(); *it_l = item; pop_back(); --it_l; } } std::sort(begin(), end(), item_cmp); } } bool ItemPtrVector::item_cmp(RegisterItem* i1, RegisterItem* i2) { const TQValueList& sortOrder = i1->parent()->sortOrder(); TQValueList::const_iterator it; int rc = 0; bool ok1, ok2; TQ_ULLONG n1, n2; MyMoneyMoney tmp; for(it = sortOrder.begin(); it != sortOrder.end(); ++it) { TransactionSortField sortField = static_cast(abs(*it)); switch(sortField) { case PostDateSort: rc = i2->sortPostDate().daysTo(i1->sortPostDate()); if(rc == 0) { rc = i1->sortSamePostDate() - i2->sortSamePostDate(); } break; case EntryDateSort: rc = i2->sortEntryDate().daysTo(i1->sortEntryDate()); break; case PayeeSort: rc = TQString::localeAwareCompare(i1->sortPayee(), i2->sortPayee()); break; case ValueSort: tmp = i1->sortValue() - i2->sortValue(); if(tmp.isZero()) rc = 0; else if(tmp.isNegative()) rc = -1; else rc = 1; break; case NoSort: // convert both values to numbers n1 = i1->sortNumber().toULongLong(&ok1); n2 = i2->sortNumber().toULongLong(&ok2); // the following four cases exist: // a) both are converted correct // compare them directly // b) n1 is numeric, n2 is not // numbers come first, so return -1 // c) n1 is not numeric, n2 is // numbers come first, so return 1 // d) both are non numbers // compare using localeAwareCompare if(ok1 && ok2) { // case a) rc = (n1 > n2) ? 1 : ((n1 == n2 ) ? 0 : -1); } else if(ok1 && !ok2) { rc = -1; } else if(!ok1 && ok2) { rc = 1; } else rc = TQString::localeAwareCompare(i1->sortNumber(), i2->sortNumber()); break; case EntryOrderSort: rc = TQString::compare(i1->sortEntryOrder(), i2->sortEntryOrder()); break; case TypeSort: rc = i1->sortType() - i2->sortType(); break; case CategorySort: rc = TQString::localeAwareCompare(i1->sortCategory(), i2->sortCategory()); break; case ReconcileStateSort: rc = static_cast(i1->sortReconcileState()) - static_cast(i2->sortReconcileState()); break; case SecuritySort: rc = TQString::localeAwareCompare(i1->sortSecurity(), i2->sortSecurity()); break; default: tqDebug("Invalid sort key %d", *it); break; } // the items differ for this sort key so we can return a result if(rc != 0) return (*it < 0) ? rc >= 0 : rc < 0; } if(rc == 0) { rc = TQString::compare(i1->sortEntryOrder(), i2->sortEntryOrder()); } return rc < 0; } GroupMarker::GroupMarker(Register *parent, const TQString& txt) : RegisterItem(parent), m_txt(txt), m_drawCounter(parent->drawCounter()-1), // make sure we get painted the first time around m_showDate(false) { int h; if(m_parent) { h = m_parent->rowHeightHint(); } else { TQFontMetrics fm( KMyMoneyGlobalSettings::listCellFont() ); h = fm.lineSpacing()+6; } if(m_bg && (m_bg->height() != h)) { delete m_bg; m_bg = 0; } // convert the backgroud once if(m_bg == 0) { m_bg = new TQPixmap; m_bg->loadFromData(fancymarker_bg_image, sizeof(fancymarker_bg_image)); // for now, we can't simply resize the m_bg member as TQt does not support // alpha resizing. So we take the (slow) detour through a TQImage object // which is ok, since we do this only once. // m_bg->resize(m_bg->width(), h); TQImage img(m_bg->convertToImage()); img = img.smoothScale(img.width(), h); m_bg->convertFromImage(img); } ++m_bgRefCnt; } GroupMarker::~GroupMarker() { --m_bgRefCnt; if(!m_bgRefCnt) { delete m_bg; m_bg = 0; } } void GroupMarker::paintRegisterCell(TQPainter* painter, int row, int /* col */, const TQRect& _r, bool /*selected*/, const TQColorGroup& _cg) { // avoid painting the marker twice for the same update round unsigned int drawCounter = m_parent->drawCounter(); if(m_drawCounter == drawCounter) { return; } m_drawCounter = drawCounter; TQRect r(_r); painter->save(); painter->translate(-r.x(), -r.y()); // the group marker always uses all cols r.setX(m_parent->columnPos(0)); r.setWidth(m_parent->visibleWidth()); painter->translate(r.x(), r.y()); TQRect cellRect; cellRect.setX(0); cellRect.setY(0); cellRect.setWidth(m_parent->visibleWidth()); cellRect.setHeight(m_parent->rowHeight(row + m_startRow)); // clear out cell rectangle TQColorGroup cg(_cg); setupColors(cg); TQBrush backgroundBrush(cg.base()); painter->fillRect(cellRect, backgroundBrush); painter->setPen(KMyMoneyGlobalSettings::listGridColor()); painter->drawLine(cellRect.x(), cellRect.height()-1, cellRect.width(), cellRect.height()-1); // now write the text painter->setPen(cg.text()); TQFont font = painter->font(); font.setBold(true); painter->setFont(font); painter->drawText(cellRect, TQt::AlignVCenter | TQt::AlignCenter, m_txt); cellRect.setHeight(m_bg->height()); int curWidth = m_bg->width(); // if the background image is too small (not wide enough) we need to increase its width. if(curWidth < cellRect.width()) { TQPixmap* newPic = new TQPixmap(cellRect.width(), cellRect.height()); int x = 0; while(x < cellRect.width()) { copyBlt(newPic, x, 0, m_bg, 0, 0, curWidth, m_bg->height()); x += curWidth; } delete m_bg; m_bg = newPic; } // now it's time to draw the background painter->drawPixmap(cellRect, *m_bg); // translate back painter->translate(-r.x(), -r.y()); // in case we need to show the date, we just paint it in col 1 if(m_showDate) { r.setX(m_parent->columnPos(1)); r.setWidth(m_parent->columnWidth(1)); painter->translate(r.x(), r.y()); cellRect.setX(0); cellRect.setY(0); cellRect.setWidth(m_parent->columnWidth(1)); cellRect.setHeight(m_parent->rowHeight(row + m_startRow)); font.setBold(false); painter->setFont(font); painter->drawText(cellRect, TQt::AlignVCenter | TQt::AlignCenter, TDEGlobal::locale()->formatDate(sortPostDate(), true)); } painter->restore(); } void GroupMarker::setupColors(TQColorGroup& cg) { cg.setColor(TQColorGroup::Base, KMyMoneyGlobalSettings::groupMarkerColor()); } int GroupMarker::rowHeightHint(void) const { if(!m_visible) return 0; return m_bg->height(); } StatementGroupMarker::StatementGroupMarker(Register* parent, CashFlowDirection dir, const TQDate& date, const TQString& txt) : FancyDateGroupMarker(parent, date, txt), m_dir(dir) { m_showDate = true; } FancyDateGroupMarker::FancyDateGroupMarker(Register* parent, const TQDate& date, const TQString& txt) : GroupMarker(parent, txt), m_date(date) { } FiscalYearGroupMarker::FiscalYearGroupMarker(Register* parent, const TQDate& date, const TQString& txt) : FancyDateGroupMarker(parent, date, txt) { } void FiscalYearGroupMarker::setupColors(TQColorGroup& cg) { cg.setColor(TQColorGroup::Base, KMyMoneyGlobalSettings::groupMarkerColor()); } SimpleDateGroupMarker::SimpleDateGroupMarker(Register* parent, const TQDate& date, const TQString& txt) : FancyDateGroupMarker(parent, date, txt) { } int SimpleDateGroupMarker::rowHeightHint(void) const { if(!m_visible) return 0; return RegisterItem::rowHeightHint() / 2; } void SimpleDateGroupMarker::paintRegisterCell(TQPainter* painter, int row, int /*col*/, const TQRect& _r, bool /*selected*/, const TQColorGroup& _cg) { TQRect r(_r); painter->save(); painter->translate(-r.x(), -r.y()); // the group marker always uses all cols r.setX(m_parent->columnPos(0)); r.setWidth(m_parent->visibleWidth()); painter->translate(r.x(), r.y()); TQRect cellRect; cellRect.setX(0); cellRect.setY(0); cellRect.setWidth(m_parent->visibleWidth()); cellRect.setHeight(m_parent->rowHeight(row + m_startRow)); // clear out cell rectangle TQColorGroup cg(_cg); if(m_alternate) cg.setColor(TQColorGroup::Base, KMyMoneyGlobalSettings::listColor()); else cg.setColor(TQColorGroup::Base, KMyMoneyGlobalSettings::listBGColor()); TQBrush backgroundBrush(cg.base()); // backgroundBrush.setStyle(TQt::DiagCrossPattern); backgroundBrush.setStyle(Qt::Dense5Pattern); backgroundBrush.setColor(KMyMoneyGlobalSettings::listGridColor()); painter->eraseRect(cellRect); painter->fillRect(cellRect, backgroundBrush); painter->setPen(KMyMoneyGlobalSettings::listGridColor()); painter->drawLine(cellRect.x(), cellRect.height()-1, cellRect.width(), cellRect.height()-1); painter->restore(); } TypeGroupMarker::TypeGroupMarker(Register* parent, CashFlowDirection dir, MyMoneyAccount::accountTypeE accType) : GroupMarker(parent), m_dir(dir) { switch(dir) { case Deposit: m_txt = i18n("Deposits onto account", "Deposits"); if(accType == MyMoneyAccount::CreditCard) { m_txt = i18n("Payments towards credit card", "Payments"); } break; case Payment: m_txt = i18n("Payments made from account", "Payments"); if(accType == MyMoneyAccount::CreditCard) { m_txt = i18n("Payments made with credit card", "Charges"); } break; default: tqDebug("Unknown CashFlowDirection %d for TypeGroupMarker constructor", dir); break; } } PayeeGroupMarker::PayeeGroupMarker(Register* parent, const TQString& name) : GroupMarker(parent, name) { } CategoryGroupMarker::CategoryGroupMarker(Register* parent, const TQString& category) : GroupMarker(parent, category) { } ReconcileGroupMarker::ReconcileGroupMarker(Register* parent, MyMoneySplit::reconcileFlagE state) : GroupMarker(parent), m_state(state) { switch(state) { case MyMoneySplit::NotReconciled: m_txt = i18n("Reconcile state 'Not reconciled'", "Not reconciled"); break; case MyMoneySplit::Cleared: m_txt = i18n("Reconcile state 'Cleared'", "Cleared"); break; case MyMoneySplit::Reconciled: m_txt = i18n("Reconcile state 'Reconciled'", "Reconciled"); break; case MyMoneySplit::Frozen: m_txt = i18n("Reconcile state 'Frozen'", "Frozen"); break; default: m_txt = i18n("Unknown"); break; } } class RegisterToolTip : public TQToolTip { public: RegisterToolTip(TQWidget* parent, Register* reg); void maybeTip(const TQPoint& pos); virtual ~RegisterToolTip() {} private: Register* m_register; }; RegisterToolTip::RegisterToolTip(TQWidget* parent, Register * reg) : TQToolTip(parent), m_register(reg) { } void RegisterToolTip::maybeTip(const TQPoint& pos) { // if we update the register, there's no need to show tooltips if(!m_register->isUpdatesEnabled()) return; TQPoint cpos = m_register->viewportToContents(pos); // tqDebug("RegisterToolTip::mayBeTip(%d,%d)", cpos.x(), cpos.y()); int row = m_register->rowAt(cpos.y()); int col = m_register->columnAt(cpos.x()); RegisterItem* item = m_register->itemAtRow(row); if(!item) return; TQPoint relPos(cpos.x() - m_register->columnPos(0), cpos.y() - m_register->rowPos(item->startRow())); row = row - item->startRow(); // tqDebug("row = %d, col = %d", row, col); // tqDebug("relpos = %d,%d", relPos.x(), relPos.y()); TQString msg; TQRect rect; if(!item->maybeTip(cpos, row, col, rect, msg)) return; TQPoint tl(rect.topLeft()); TQPoint br(rect.bottomRight()); TQRect r = TQRect(m_register->contentsToViewport(tl), m_register->contentsToViewport(br)); tip(r, msg); return; } Register::Register(TQWidget *parent, const char *name ) : TransactionEditorContainer(parent, name), m_selectAnchor(0), m_focusItem(0), m_firstItem(0), m_lastItem(0), m_firstErronous(0), m_lastErronous(0), m_markErronousTransactions(0), m_rowHeightHint(0), m_ledgerLensForced(false), m_selectionMode(Multi), m_listsDirty(false), m_ignoreNextButtonRelease(false), m_needInitialColumnResize(false), m_buttonState(TQt::ButtonState(0)), m_drawCounter(0) { m_tooltip = new RegisterToolTip(viewport(), this); setNumCols(MaxColumns); setCurrentCell(0, 1); // we do our own sorting setSorting(false); // keep the following list in sync with KMyMoneyRegister::Column in transaction.h horizontalHeader()->setLabel(NumberColumn, i18n("No.")); horizontalHeader()->setLabel(DateColumn, i18n("Date")); horizontalHeader()->setLabel(AccountColumn, i18n("Account")); horizontalHeader()->setLabel(SecurityColumn, i18n("Security")); horizontalHeader()->setLabel(DetailColumn, i18n("Details")); horizontalHeader()->setLabel(ReconcileFlagColumn, i18n("C")); horizontalHeader()->setLabel(PaymentColumn, i18n("Payment")); horizontalHeader()->setLabel(DepositColumn, i18n("Deposit")); horizontalHeader()->setLabel(QuantityColumn, i18n("Quantity")); horizontalHeader()->setLabel(PriceColumn, i18n("Price")); horizontalHeader()->setLabel(ValueColumn, i18n("Value")); horizontalHeader()->setLabel(BalanceColumn, i18n("Balance")); setLeftMargin(0); verticalHeader()->hide(); for(int i = 0; i < numCols(); ++i) setColumnStretchable(i, false); horizontalHeader()->setResizeEnabled(false); horizontalHeader()->setMovingEnabled(false); horizontalHeader()->setClickEnabled(false); horizontalHeader()->installEventFilter(this); // never show horizontal scroll bars setHScrollBarMode(TQScrollView::AlwaysOff); connect(this, TQT_SIGNAL(clicked(int, int, int, const TQPoint&)), this, TQT_SLOT(selectItem(int, int, int, const TQPoint&))); connect(this, TQT_SIGNAL(doubleClicked(int, int, int, const TQPoint&)), this, TQT_SLOT(slotDoubleClicked(int, int, int, const TQPoint&))); // double clicking the header turns on auto column sizing connect(horizontalHeader(), TQT_SIGNAL(sectionSizeChanged(int)), this, TQT_SLOT(slotAutoColumnSizing(int))); //DND setAcceptDrops(true); } // DND Transaction* Register::dropTransaction(TQPoint cPos) const { Transaction* t = 0; cPos -= TQPoint( verticalHeader()->width(), horizontalHeader()->height() ); if(cPos.y() >= 0) { cPos += TQPoint(contentsX(), contentsY()); int row = rowAt(cPos.y()); t = dynamic_cast(itemAtRow(row)); } return t; } void Register::dragMoveEvent(TQDragMoveEvent* event) { if ( KURLDrag::canDecode(event) ) { event->ignore(); Transaction* t = dropTransaction(event->pos()); if(t && !t->isScheduled()) { event->accept(); } } } void Register::dropEvent(TQDropEvent* event) { tqDebug("Register::dropEvent"); if ( KURLDrag::canDecode(event) ) { event->ignore(); Transaction* t = dropTransaction(event->pos()); if(t && !t->isScheduled()) { tqDebug("Drop was ok"); KURL::List urls; KURLDrag::decode(event, urls); tqDebug(TQString("List is '%s'").arg(urls.toStringList().join(";"))); event->accept(); } } } // DND end Register::~Register() { clear(); delete m_tooltip; m_tooltip = 0; } void Register::slotAutoColumnSizing(int section) { Q_UNUSED(section) #if 0 // this is some trial code to make the col sizes adjustable // there are some drawbacks though: what when we have a register // but no account? (ipwizard 2007-11-06) if(isUpdatesEnabled()) { int w = visibleWidth(); TQString size; for(int i=0; i < numCols(); ++i) { if(i) size += ","; if(i == DetailColumn) { size += "0"; continue; } size += TQString("%1").arg((columnWidth(i) * 100) / w); } tqDebug(TQString("size = %1").arg(size)); m_account.setValue("kmm-ledger-column-width", size); } #endif } bool Register::eventFilter(TQObject* o, TQEvent* e) { if(TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(horizontalHeader()) && e->type() == TQEvent::MouseButtonPress) { TQMouseEvent *me = dynamic_cast(e); if(me->button() == Qt::RightButton) { emit headerClicked(); } // eat up left mouse button press for now return true; } else if(TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(horizontalHeader()) && e->type() == TQEvent::Paint) { // always show the header in bold (to suppress cell selection) TQFont f(horizontalHeader()->font()); f.setBold(true); horizontalHeader()->setFont(f); } else if(TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(this) && e->type() == TQEvent::KeyPress) { TQKeyEvent* ke = dynamic_cast(e); if(ke->key() == TQt::Key_Menu) { emit openContextMenu(); return true; } } return TQTable::eventFilter(o, e); } void Register::setupRegister(const MyMoneyAccount& account, const TQValueList& cols) { m_account = account; bool enabled = isUpdatesEnabled(); setUpdatesEnabled(false); for(int i = 0; i < MaxColumns; ++i) hideColumn(i); m_needInitialColumnResize = true; m_lastCol = static_cast(0); TQValueList::const_iterator it_c; for(it_c = cols.begin(); it_c != cols.end(); ++it_c) { if((*it_c) > MaxColumns) continue; showColumn(*it_c); if(*it_c > m_lastCol) m_lastCol = *it_c; } setUpdatesEnabled(enabled); } void Register::setupRegister(const MyMoneyAccount& account, bool showAccountColumn) { m_account = account; bool enabled = isUpdatesEnabled(); setUpdatesEnabled(false); for(int i = 0; i < MaxColumns; ++i) hideColumn(i); horizontalHeader()->setLabel(PaymentColumn, i18n("Payment made from account", "Payment")); horizontalHeader()->setLabel(DepositColumn, i18n("Deposit into account", "Deposit")); if(account.id().isEmpty()) { setUpdatesEnabled(enabled); return; } m_needInitialColumnResize = true; // turn on standard columns showColumn(DateColumn); showColumn(DetailColumn); showColumn(ReconcileFlagColumn); // balance switch(account.accountType()) { case MyMoneyAccount::Stock: break; default: showColumn(BalanceColumn); break; } // Number column switch(account.accountType()) { case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::Loan: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Equity: if(KMyMoneyGlobalSettings::alwaysShowNrField()) showColumn(NumberColumn); break; case MyMoneyAccount::Checkings: case MyMoneyAccount::CreditCard: showColumn(NumberColumn); break; default: hideColumn(NumberColumn); break; } switch(account.accountType()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: showAccountColumn = true; break; default: break; } if(showAccountColumn) showColumn(AccountColumn); // Security, activity, payment, deposit, amount, price and value column switch(account.accountType()) { default: showColumn(PaymentColumn); showColumn(DepositColumn); break; case MyMoneyAccount::Investment: showColumn(SecurityColumn); showColumn(QuantityColumn); showColumn(PriceColumn); showColumn(ValueColumn); break; } // headings switch(account.accountType()) { case MyMoneyAccount::CreditCard: horizontalHeader()->setLabel(PaymentColumn, i18n("Payment made with credit card", "Charge")); horizontalHeader()->setLabel(DepositColumn, i18n("Payment towards credit card", "Payment")); break; case MyMoneyAccount::Asset: case MyMoneyAccount::AssetLoan: horizontalHeader()->setLabel(PaymentColumn, i18n("Decrease of asset/liability value", "Decrease")); horizontalHeader()->setLabel(DepositColumn, i18n("Increase of asset/liability value", "Increase")); break; case MyMoneyAccount::Liability: case MyMoneyAccount::Loan: horizontalHeader()->setLabel(PaymentColumn, i18n("Increase of asset/liability value", "Increase")); horizontalHeader()->setLabel(DepositColumn, i18n("Decrease of asset/liability value", "Decrease")); break; case MyMoneyAccount::Income: case MyMoneyAccount::Expense: horizontalHeader()->setLabel(PaymentColumn, i18n("Income")); horizontalHeader()->setLabel(DepositColumn, i18n("Expense")); break; default: break; } switch(account.accountType()) { default: m_lastCol = BalanceColumn; break; } setUpdatesEnabled(enabled); } bool Register::focusNextPrevChild(bool next) { return TQFrame::focusNextPrevChild(next); } void Register::setSortOrder(const TQString& order) { TQStringList orderList = TQStringList::split(",", order); TQStringList::const_iterator it; m_sortOrder.clear(); for(it = orderList.begin(); it != orderList.end(); ++it) { m_sortOrder << static_cast((*it).toInt()); } } void Register::sortItems(void) { if(m_items.count() == 0) return; // sort the array of pointers to the transactions m_items.sort(); // update the next/prev item chains RegisterItem* prev = 0; RegisterItem* item; m_firstItem = m_lastItem = 0; for(TQValueVector::size_type i = 0; i < m_items.size(); ++i) { item = m_items[i]; if(!item) continue; if(!m_firstItem) m_firstItem = item; m_lastItem = item; if(prev) prev->setNextItem(item); item->setPrevItem(prev); item->setNextItem(0); prev = item; } // update the balance visibility settings item = m_lastItem; bool showBalance = true; while(item) { Transaction* t = dynamic_cast(item); if(t) { t->setShowBalance(showBalance); if(!t->isVisible()) { showBalance = false; } } item = item->prevItem(); } // force update of the item index (row to item array) m_listsDirty = true; } TransactionSortField Register::primarySortKey(void) const { if(!m_sortOrder.isEmpty()) return static_cast(abs(m_sortOrder.first())); return UnknownSort; } void Register::clear(void) { m_firstErronous = m_lastErronous = 0; m_ensureVisibleItem = 0; RegisterItem* p; while((p = firstItem()) != 0) { delete p; } m_items.clear(); m_firstItem = m_lastItem = 0; m_listsDirty = true; m_selectAnchor = 0; m_focusItem = 0; #ifndef KMM_DESIGNER // recalculate row height hint TQFontMetrics fm( KMyMoneyGlobalSettings::listCellFont() ); m_rowHeightHint = fm.lineSpacing()+6; #endif m_needInitialColumnResize = true; } void Register::insertItemAfter(RegisterItem*p, RegisterItem* prev) { RegisterItem* next = 0; if(!prev) prev = lastItem(); if(prev) { next = prev->nextItem(); prev->setNextItem(p); } if(next) next->setPrevItem(p); p->setPrevItem(prev); p->setNextItem(next); if(!m_firstItem) m_firstItem = p; if(!m_lastItem) m_lastItem = p; if(prev == m_lastItem) m_lastItem = p; m_listsDirty = true; } void Register::addItem(RegisterItem* p) { RegisterItem* q = lastItem(); if(q) q->setNextItem(p); p->setPrevItem(q); p->setNextItem(0); m_items.append(p); if(!m_firstItem) m_firstItem = p; m_lastItem = p; m_listsDirty = true; } void Register::removeItem(RegisterItem* p) { // remove item from list if(p->prevItem()) p->prevItem()->setNextItem(p->nextItem()); if(p->nextItem()) p->nextItem()->setPrevItem(p->prevItem()); // update first and last pointer if required if(p == m_firstItem) m_firstItem = p->nextItem(); if(p == m_lastItem) m_lastItem = p->prevItem(); // make sure we don't do it twice p->setNextItem(0); p->setPrevItem(0); // remove it from the m_items array for(TQValueVector::size_type i = 0; i < m_items.size(); ++i) { RegisterItem* item = m_items[i]; if(!item) continue; if(item == p) { m_items[i] = 0; break; } } m_listsDirty = true; } RegisterItem* Register::firstItem(void) const { return m_firstItem; } RegisterItem* Register::lastItem(void) const { return m_lastItem; } void Register::setupItemIndex(int rowCount) { // setup index array m_itemIndex.clear(); m_itemIndex.reserve(rowCount); // fill index array rowCount = 0; RegisterItem* prev = 0; m_firstItem = m_lastItem = 0; for(TQValueVector::size_type i = 0; i < m_items.size(); ++i) { RegisterItem* item = m_items[i]; if(!item) continue; if(!m_firstItem) m_firstItem = item; m_lastItem = item; if(prev) prev->setNextItem(item); item->setPrevItem(prev); item->setNextItem(0); prev = item; for(int j = item->numRowsRegister(); j; --j) { m_itemIndex.push_back(item); } } } void Register::drawContents( TQPainter *p, int cx, int cy, int cw, int ch ) { // the TQTable::drawContents() method does not honor the block update flag // so we take care of it here if ( testWState((TQt::WidgetState)(WState_Visible|WState_BlockUpdates)) != WState_Visible ) return; if(m_listsDirty) { updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm()); } ++m_drawCounter; TQTable::drawContents(p, cx, cy, cw, ch); } void Register::updateAlternate(void) const { bool alternate = false; for(TQValueVector::size_type i = 0; i < m_items.size(); ++i) { RegisterItem* item = m_items[i]; if(!item) continue; if(item->isVisible()) { item->setAlternate(alternate); alternate ^= true; } } } void Register::suppressAdjacentMarkers(void) { bool lastWasGroupMarker = false; KMyMoneyRegister::RegisterItem* p = lastItem(); KMyMoneyRegister::Transaction* t = dynamic_cast(p); if(t && t->transaction().id().isEmpty()) { lastWasGroupMarker = true; p = p->prevItem(); } while(p) { KMyMoneyRegister::GroupMarker* m = dynamic_cast(p); if(m) { // make adjacent group marker invisible except those that show statement information if(lastWasGroupMarker && (dynamic_cast(m) == 0)) { m->setVisible(false); } lastWasGroupMarker = true; } else if(p->isVisible()) lastWasGroupMarker = false; p = p->prevItem(); } } void Register::updateRegister(bool forceUpdateRowHeight) { ::timetrace("Update register"); if(m_listsDirty || forceUpdateRowHeight) { // don't get in here recursively m_listsDirty = false; int rowCount = 0; // determine the number of rows we need to display all items // while going through the list, check for erronous transactions for(TQValueVector::size_type i = 0; i < m_items.size(); ++i) { RegisterItem* item = m_items[i]; if(!item) continue; item->setStartRow(rowCount); item->setNeedResize(); rowCount += item->numRowsRegister(); if(item->isErronous()) { if(!m_firstErronous) m_firstErronous = item; m_lastErronous = item; } } updateAlternate(); // create item index setupItemIndex(rowCount); bool needUpdateHeaders = (numRows() != rowCount) | forceUpdateRowHeight; // setup TQTable. Make sure to suppress screen updates for now bool updatesEnabled = isUpdatesEnabled(); setUpdatesEnabled(false); setNumRows(rowCount); // if we need to update the headers, we do it now for all rows // again we make sure to suppress screen updates if(needUpdateHeaders) { // int height = rowHeightHint(); verticalHeader()->setUpdatesEnabled(false); for(int i = 0; i < rowCount; ++i) { RegisterItem* item = itemAtRow(i); if(item->isVisible()) { showRow(i); } else { hideRow(i); } verticalHeader()->resizeSection(i, item->rowHeightHint()); } verticalHeader()->setUpdatesEnabled(true); } // add or remove scrollbars as required updateScrollBars(); setUpdatesEnabled(updatesEnabled); // force resizeing of the columns if necessary if(m_needInitialColumnResize) { TQTimer::singleShot(0, this, TQT_SLOT(resize())); m_needInitialColumnResize = false; } else { updateContents(); // if the number of rows changed, we might need to resize the register // to make sure we reflect the current visibility of the scrollbars. if(needUpdateHeaders) TQTimer::singleShot(0, this, TQT_SLOT(resize())); } } ::timetrace("Done updateing register"); } int Register::rowHeightHint(void) const { if(!m_rowHeightHint) { tqDebug("Register::rowHeightHint(): m_rowHeightHint is zero!!"); } return m_rowHeightHint; } void Register::paintCell(TQPainter* painter, int row, int col, const TQRect& r, bool selected, const TQColorGroup& cg) { // determine the item that we need to paint in the row and call it's paintRegisterCell() method if((row < 0) || ((unsigned)row > m_itemIndex.size())) { tqDebug("Register::paintCell: row %d out of bounds %d", row, (int)m_itemIndex.size()); return; } // tqDebug("paintCell(%d,%d)", row, col); RegisterItem* const item = m_itemIndex[row]; item->paintRegisterCell(painter, row - item->startRow(), col, r, selected, cg); } void Register::focusInEvent(TQFocusEvent* ev) { TQTable::focusInEvent(ev); if(m_focusItem) { m_focusItem->setFocus(true, false); repaintItems(m_focusItem); } } void Register::focusOutEvent(TQFocusEvent* ev) { if(m_focusItem) { m_focusItem->setFocus(false, false); repaintItems(m_focusItem); } TQTable::focusOutEvent(ev); } void Register::resize(void) { resize(DetailColumn); } void Register::resize(int col) { bool enabled = isUpdatesEnabled(); setUpdatesEnabled(false); // resize the register int w = visibleWidth(); // TODO I was playing a bit with manual ledger resizing but could not get // a good solution. I just leave the code around, so that maybe others // pick it up again. So far, it's not clear to me where to store the // size of the sections: // // a) with the account (as it is done now) // b) with the application for the specific account type // c) ???? // // Ideas are welcome (ipwizard: 2007-07-19) // Note: currently there's no way to switch back to automatic // column sizing once the manual sizing option has been saved #if 0 if(m_account.value("kmm-ledger-column-width").isEmpty()) { #endif // check which space we need if(columnWidth(NumberColumn)) adjustColumn(NumberColumn); if(columnWidth(AccountColumn)) adjustColumn(AccountColumn); if(columnWidth(PaymentColumn)) adjustColumn(PaymentColumn); if(columnWidth(DepositColumn)) adjustColumn(DepositColumn); if(columnWidth(BalanceColumn)) adjustColumn(BalanceColumn); if(columnWidth(PriceColumn)) adjustColumn(PriceColumn); if(columnWidth(ValueColumn)) adjustColumn(ValueColumn); // make amount columns all the same size // only extend the entry columns to make sure they fit // the widget int dwidth = 0; int ewidth = 0; if(ewidth < columnWidth(PaymentColumn)) ewidth = columnWidth(PaymentColumn); if(ewidth < columnWidth(DepositColumn)) ewidth = columnWidth(DepositColumn); if(dwidth < columnWidth(BalanceColumn)) dwidth = columnWidth(BalanceColumn); if(ewidth < columnWidth(PriceColumn)) ewidth = columnWidth(PriceColumn); if(dwidth < columnWidth(ValueColumn)) dwidth = columnWidth(ValueColumn); int swidth = columnWidth(SecurityColumn); if(swidth > 0) { adjustColumn(SecurityColumn); swidth = columnWidth(SecurityColumn); } #ifndef KMM_DESIGNER // Resize the date and money fields to either // a) the size required by the input widget if no transaction form is shown // b) the adjusted value for the input widget if the transaction form is visible if(!KMyMoneyGlobalSettings::transactionForm()) { kMyMoneyDateInput* dateField = new kMyMoneyDateInput; kMyMoneyEdit* valField = new kMyMoneyEdit; dateField->setFont(KMyMoneyGlobalSettings::listCellFont()); setColumnWidth(DateColumn, dateField->minimumSizeHint().width()); valField->setMinimumWidth(ewidth); ewidth = valField->minimumSizeHint().width(); if(swidth > 0) { swidth = columnWidth(SecurityColumn) + 40; } delete valField; delete dateField; } else { adjustColumn(DateColumn); } #endif if(columnWidth(PaymentColumn)) setColumnWidth(PaymentColumn, ewidth); if(columnWidth(DepositColumn)) setColumnWidth(DepositColumn, ewidth); if(columnWidth(BalanceColumn)) setColumnWidth(BalanceColumn, dwidth); if(columnWidth(PriceColumn)) setColumnWidth(PriceColumn, ewidth); if(columnWidth(ValueColumn)) setColumnWidth(ValueColumn, dwidth); if(columnWidth(ReconcileFlagColumn)) setColumnWidth(ReconcileFlagColumn, 20); if(swidth > 0) setColumnWidth(SecurityColumn, swidth); #if 0 // see comment above } else { TQStringList colSizes = TQStringList::split(",", m_account.value("kmm-ledger-column-width"), true); for(int i; i < colSizes.count(); ++i) { int colWidth = colSizes[i].toInt(); if(colWidth == 0) continue; setColumnWidth(i, w * colWidth / 100); } } #endif for(int i = 0; i < numCols(); ++i) { if(i == col) continue; w -= columnWidth(i); } setColumnWidth(col, w); setUpdatesEnabled(enabled); updateContents(); } void Register::adjustColumn(int col) { #ifdef KMM_DESIGNER Q_UNUSED(col) #else TQString msg = "%1 adjusting column %2"; ::timetrace((msg.arg("Start").arg(col)).ascii()); TQHeader *topHeader = horizontalHeader(); TQFontMetrics cellFontMetrics(KMyMoneyGlobalSettings::listCellFont()); int w = topHeader->fontMetrics().width( topHeader->label( col ) ) + 10; if ( topHeader->iconSet( col ) ) w += topHeader->iconSet( col )->pixmap().width(); w = TQMAX( w, 20 ); int maxWidth = 0; switch(col) { case NumberColumn: maxWidth = cellFontMetrics.width("0123456789"); break; default: break; } // check for date column if(col == DateColumn) { TQString txt = TDEGlobal::locale()->formatDate(TQDate(6999,12,29), true); int nw = cellFontMetrics.width(txt+" "); w = TQMAX( w, nw ); } else { // scan through the transactions for(unsigned i = 0; i < m_items.size(); ++i) { RegisterItem* const item = m_items[i]; if(!item) continue; Transaction* t = dynamic_cast(item); if(t) { int nw = t->registerColWidth(col, cellFontMetrics); w = TQMAX( w, nw ); if(maxWidth) { if(w > maxWidth) { w = maxWidth; break; } } } } } setColumnWidth( col, w ); #endif } void Register::repaintItems(RegisterItem* first, RegisterItem* last) { if(first == 0 && last == 0) { first = firstItem(); last = lastItem(); } if(first == 0) return; if(last == 0) last = first; // tqDebug("repaintItems from row %d to row %d", first->startRow(), last->startRow()+last->numRowsRegister()-1); // the following code is based on code I found in // TQTable::cellGeometry() and TQTable::updateCell() (ipwizard) TQRect cg(0, rowPos(first->startRow()), visibleWidth(), rowPos(last->startRow()+last->numRowsRegister()-1) - rowPos(first->startRow()) + rowHeight(last->startRow()+last->numRowsRegister()-1)); TQRect r(contentsToViewport(TQPoint (cg.x() - 2, cg.y() - 2 )), TQSize(cg.width() + 4, cg.height() + 4 )); TQRect tmp = m_lastRepaintRect | r; if(abs(tmp.height()) > 3000) { // make sure that the previously triggered repaint has been done before we // trigger the next. Not having this used to cause some trouble when changing // the focus within a 2000 item ledger from the last to the first item. TQApplication::eventLoop()->processEvents(TQEventLoop::ExcludeUserInput, 10); } m_lastRepaintRect = r; TQApplication::postEvent( viewport(), new TQPaintEvent( r, FALSE ) ); } void Register::clearSelection(void) { unselectItems(); } void Register::doSelectItems(int from, int to, bool selected) { int start, end; // make sure start is smaller than end if(from <= to) { start = from; end = to; } else { start = to; end = from; } // make sure we stay in bounds if(start < 0) start = 0; if((end <= -1) || ((unsigned)end > (m_items.size()-1))) end = m_items.size()-1; RegisterItem* firstItem; RegisterItem* lastItem; firstItem = lastItem = 0; for(int i = start; i <= end; ++i) { RegisterItem* const item = m_items[i]; if(item) { if(selected != item->isSelected()) { if(!firstItem) firstItem = item; item->setSelected(selected); lastItem = item; } } } // anything changed? if(firstItem || lastItem) repaintItems(firstItem, lastItem); } RegisterItem* Register::itemAtRow(int row) const { if(row >= 0 && (unsigned)row < m_itemIndex.size()) { return m_itemIndex[row]; } return 0; } int Register::rowToIndex(int row) const { for(unsigned i = 0; i < m_items.size(); ++i) { RegisterItem* const item = m_items[i]; if(!item) continue; if(row >= item->startRow() && row < (item->startRow() + item->numRowsRegister())) return i; } return -1; } void Register::selectedTransactions(SelectedTransactions& list) const { if(m_focusItem && m_focusItem->isSelected() && m_focusItem->isVisible()) { Transaction* t = dynamic_cast(m_focusItem); if(t) { TQString id; if(t->isScheduled()) id = t->transaction().id(); SelectedTransaction s(t->transaction(), t->split(), id); list << s; } } for(unsigned i = 0; i < m_items.size(); ++i) { RegisterItem* const item = m_items[i]; // make sure, we don't include the focus item twice if(item == m_focusItem) continue; if(item && item->isSelected() && item->isVisible()) { Transaction* t = dynamic_cast(item); if(t) { TQString id; if(t->isScheduled()) id = t->transaction().id(); SelectedTransaction s(t->transaction(), t->split(), id); list << s; } } } } TQValueList Register::selectedItems(void) const { TQValueList list; RegisterItem* item = m_firstItem; while(item) { if(item && item->isSelected() && item->isVisible()) { list << item; } item = item->nextItem(); } return list; } int Register::selectedItemsCount(void) const { int cnt = 0; RegisterItem* item = m_firstItem; while(item) { if(item->isSelected() && item->isVisible()) ++cnt; item = item->nextItem(); } return cnt; } void Register::contentsMouseReleaseEvent( TQMouseEvent *e ) { if(m_ignoreNextButtonRelease) { m_ignoreNextButtonRelease = false; return; } m_buttonState = e->state(); TQTable::contentsMouseReleaseEvent(e); } void Register::selectItem(int row, int col, int button, const TQPoint& /* mousePos */) { if(row >= 0 && (unsigned)row < m_itemIndex.size()) { RegisterItem* item = m_itemIndex[row]; // don't support selecting when the item has an editor // or the item itself is not selectable if(item->hasEditorOpen() || !item->isSelectable()) return; TQString id = item->id(); selectItem(item); // selectItem() might have changed the pointers, so we // need to reconstruct it here item = itemById(id); Transaction* t = dynamic_cast(item); if(t) { if(!id.isEmpty()) { switch(button & Qt::MouseButtonMask) { case Qt::RightButton: emit openContextMenu(); break; case Qt::LeftButton: if(t && col == ReconcileFlagColumn && selectedItemsCount() == 1 && !t->isScheduled()) emit reconcileStateColumnClicked(t); break; default: break; } } else { emit emptyItemSelected(); } } } } void Register::setAnchorItem(RegisterItem* anchorItem) { m_selectAnchor = anchorItem; } bool Register::setFocusItem(RegisterItem* focusItem) { if(focusItem && focusItem->canHaveFocus()) { if(m_focusItem) { m_focusItem->setFocus(false); // issue a repaint here only if we move the focus if(m_focusItem != focusItem) repaintItems(m_focusItem); } Transaction* item = dynamic_cast(focusItem); if(m_focusItem != focusItem && item) { emit focusChanged(item); } m_focusItem = focusItem; m_focusItem->setFocus(true); if(m_listsDirty) updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm()); ensureItemVisible(m_focusItem); repaintItems(m_focusItem); return true; } else return false; } bool Register::setFocusToTop(void) { RegisterItem* rgItem=m_firstItem; while (rgItem) { if (setFocusItem(rgItem)) return true; rgItem=rgItem->nextItem(); } return false; } void Register::selectItem(RegisterItem* item, bool dontChangeSelections) { if(!item) return; // kdDebug(2) << "Register::selectItem(" << item << "): type is " << typeid(*item).name() << endl; TQt::ButtonState buttonState = m_buttonState; m_buttonState = Qt::NoButton; if(m_selectionMode == NoSelection) return; if(item->isSelectable()) { TQString id = item->id(); TQValueList itemList = selectedItems(); bool okToSelect = true; int cnt = itemList.count(); bool sameEntryType = true; if(cnt > 0) { if(typeid(*itemList.begin()) != typeid(item)) sameEntryType = false; } if(buttonState & Qt::LeftButton) { if(!(buttonState & (TQt::ShiftButton | TQt::ControlButton))) { if((cnt != 1) || ((cnt == 1) && !item->isSelected())) { emit aboutToSelectItem(item, okToSelect); if(okToSelect) { // pointer 'item' might have changed. reconstruct it. item = itemById(id); unselectItems(); item->setSelected(true); setFocusItem(item); } } if(okToSelect) m_selectAnchor = item; } if(m_selectionMode == Multi) { switch(buttonState & (TQt::ShiftButton | TQt::ControlButton)) { case TQt::ControlButton: okToSelect = sameEntryType; if(typeid(*item) == typeid(StdTransactionScheduled)) okToSelect = false; // toggle selection state of current item emit aboutToSelectItem(item, okToSelect); if(okToSelect) { // pointer 'item' might have changed. reconstruct it. item = itemById(id); item->setSelected(!item->isSelected()); setFocusItem(item); } break; case TQt::ShiftButton: okToSelect = sameEntryType; if(typeid(*item) == typeid(StdTransactionScheduled)) okToSelect = false; emit aboutToSelectItem(item, okToSelect); if(okToSelect) { // pointer 'item' might have changed. reconstruct it. item = itemById(id); unselectItems(); selectItems(rowToIndex(m_selectAnchor->startRow()), rowToIndex(item->startRow())); setFocusItem(item); } break; } } } else if(buttonState & Qt::RightButton) { // if the right button is pressed then only change the // selection if none of the Shift/Ctrl button is pressed and // one of the following conditions is true: // // a) single transaction is selected // b) multiple transactions are selected and the one to be selected is not if(!(buttonState & (TQt::ShiftButton | TQt::ControlButton))) { if((cnt > 0) && (!item->isSelected())) { okToSelect = sameEntryType; emit aboutToSelectItem(item, okToSelect); if(okToSelect) { // pointer 'item' might have changed. reconstruct it. item = itemById(id); unselectItems(); item->setSelected(true); setFocusItem(item); } } if(okToSelect) m_selectAnchor = item; } } else { // we get here when called by application logic emit aboutToSelectItem(item, okToSelect); if(okToSelect) { // pointer 'item' might have changed. reconstruct it. item = itemById(id); if(!dontChangeSelections) unselectItems(); item->setSelected(true); setFocusItem(item); m_selectAnchor = item; } } if(okToSelect) { SelectedTransactions list(this); emit selectionChanged(list); } } } void Register::ensureItemVisible(RegisterItem* item) { if(!item) return; m_ensureVisibleItem = item; TQTimer::singleShot(0, this, TQT_SLOT(slotEnsureItemVisible())); } void Register::slotDoubleClicked(int row, int, int, const TQPoint&) { if(row >= 0 && (unsigned)row < m_itemIndex.size()) { RegisterItem* p = m_itemIndex[row]; if(p->isSelectable()) { m_ignoreNextButtonRelease = true; // double click to start editing only works if the focus // item is among the selected ones if(!focusItem()) { setFocusItem(p); if(m_selectionMode != NoSelection) p->setSelected(true); } if(m_focusItem->isSelected()) { // don't emit the signal right away but wait until // we come back to the TQt main loop TQTimer::singleShot(0, this, TQT_SIGNAL(editTransaction())); } } } } void Register::slotEnsureItemVisible(void) { // if clear() has been called since the timer was // started, we just ignore the call if(!m_ensureVisibleItem) return; // make sure to catch latest changes bool enabled = isUpdatesEnabled(); setUpdatesEnabled(false); updateRegister(); setUpdatesEnabled(enabled); RegisterItem* item = m_ensureVisibleItem; RegisterItem* prev = item->prevItem(); while(prev && !prev->isVisible()) prev = prev->prevItem(); RegisterItem* next = item->nextItem(); while(next && !next->isVisible()) next = next->nextItem(); int rowPrev, rowNext; rowPrev = item->startRow(); rowNext = item->startRow() + item->numRowsRegister() - 1; if(prev) rowPrev = prev->startRow(); if(next) rowNext = next->startRow() + next->numRowsRegister() - 1; if(rowPrev < 0) rowPrev = 0; if(rowNext >= numRows()) rowNext = numRows()-1; int wt = contentsY(); // window top int wh = visibleHeight(); // window height int lt = rowPos(rowPrev); // top of line above lens int lb = rowPos(rowNext)+rowHeight(rowNext); // bottom of line below lens // only update widget, if the transaction is not fully visible if(lt < wt || lb >= (wt + wh)) { if(rowPrev >= 0) { ensureCellVisible(rowPrev, 0); } ensureCellVisible(item->startRow(), 0); if(rowNext < numRows()) { ensureCellVisible(rowNext, 0); } } } TransactionSortField KMyMoneyRegister::textToSortOrder(const TQString& text) { for(int idx = 1; idx < static_cast(MaxSortFields); ++idx) { if(text == i18n(sortOrderText[idx].utf8())) { return static_cast(idx); } } return UnknownSort; } const TQString KMyMoneyRegister::sortOrderToText(TransactionSortField idx) { if(idx < PostDateSort || idx >= MaxSortFields) idx = UnknownSort; return i18n(sortOrderText[idx].utf8()); } TQString Register::text(int /*row*/, int /*col*/) const { return TQString("a"); } TQWidget* Register::cellWidget(int row, int col) const { // separeted here in two if()s, because this method is called for each // event from TQTable::eventFilter and in the most cases it is -1, -1 if(row < 0 || col < 0) return 0; if(row > numRows() - 1 || col > numCols() - 1) { if(numRows() && numCols()) tqWarning("Register::cellWidget(%d,%d) out of bounds (%d,%d)", row, col, numRows(), numCols()); return 0; } if(!m_cellWidgets.count()) return 0; TQWidget* w = 0; TQPair idx = tqMakePair(row, col); TQMap, TQWidget*>::const_iterator it_w; it_w = m_cellWidgets.find(idx); if(it_w != m_cellWidgets.end()) w = *it_w; return w; } void Register::insertWidget(int row, int col, TQWidget* w) { if(row < 0 || col < 0 || row > numRows() - 1 || col > numCols() - 1) { tqWarning("Register::insertWidget(%d,%d) out of bounds", row, col); return; } TQPair idx = tqMakePair(row, col); m_cellWidgets[idx] = w; } void Register::clearCellWidget(int row, int col) { if(row < 0 || col < 0 || row > numRows() - 1 || col > numCols() - 1) { tqWarning("Register::clearCellWidget(%d,%d) out of bounds", row, col); return; } TQPair idx = tqMakePair(row, col); TQMap, TQWidget*>::iterator it_w; it_w = m_cellWidgets.find(idx); if(it_w != m_cellWidgets.end()) { (*it_w)->deleteLater(); m_cellWidgets.remove(it_w); } } TQWidget* Register::createEditor(int /*row*/, int /*col*/, bool /*initFromCell*/) const { return 0; } void Register::setCellContentFromEditor(int /*row*/, int /*col*/) { } void Register::endEdit(int /*row*/, int /*col*/, bool /*accept*/, bool /*replace*/) { } void Register::arrangeEditWidgets(TQMap& editWidgets, KMyMoneyRegister::Transaction* t) { t->arrangeWidgetsInRegister(editWidgets); ensureItemVisible(t); // updateContents(); } void Register::tabOrder(TQWidgetList& tabOrderWidgets, KMyMoneyRegister::Transaction* t) const { t->tabOrderInRegister(tabOrderWidgets); } void Register::removeEditWidgets(TQMap& editWidgets) { // remove pointers from map TQMap::iterator it; for(it = editWidgets.begin(); it != editWidgets.end(); ) { if((*it)->parentWidget() == this) { editWidgets.remove(it); it = editWidgets.begin(); } else ++it; } // now delete the widgets KMyMoneyRegister::Transaction* t = dynamic_cast(focusItem()); for(int row = t->startRow(); row < t->startRow() + t->numRowsRegister(true); ++row) { for(int col = 0; col < numCols(); ++col) { if(cellWidget(row, col)) clearCellWidget(row, col); } // make sure to reduce the possibly size to what it was before editing started setRowHeight(row, t->rowHeightHint()); } } void Register::slotToggleErronousTransactions(void) { // toggle switch m_markErronousTransactions ^= 1; // check if anything needs to be redrawn KMyMoneyRegister::RegisterItem* p = m_firstErronous; while(p && p->prevItem() != m_lastErronous) { if(p->isErronous()) repaintItems(p); p = p->nextItem(); } // restart timer TQTimer::singleShot(500, this, TQT_SLOT(slotToggleErronousTransactions())); } RegisterItem* Register::itemById(const TQString& id) const { if(id.isEmpty()) return m_lastItem; for(TQValueVector::size_type i = 0; i < m_items.size(); ++i) { RegisterItem* item = m_items[i]; if(!item) continue; if(item->id() == id) return item; } return 0; } void Register::handleItemChange(RegisterItem* old, bool shift, bool control) { if(m_selectionMode == Multi) { if(shift) { selectRange(m_selectAnchor ? m_selectAnchor : old, m_focusItem, false, true, (m_selectAnchor && !control) ? true : false); } else if(!control) { selectItem(m_focusItem, false); } } } void Register::selectRange(RegisterItem* from, RegisterItem* to, bool invert, bool includeFirst, bool clearSel) { if(!from || !to) return; if(from == to && !includeFirst) return; bool swap = false; if(to == from->prevItem()) swap = true; RegisterItem* item; if(!swap && from != to && from != to->prevItem()) { bool found = false; for(item = from; item; item = item->nextItem()) { if(item == to) { found = true; break; } } if(!found) swap = true; } if(swap) { item = from; from = to; to = item; if(!includeFirst) to = to->prevItem(); } else if(!includeFirst) { from = from->nextItem(); } bool changed = false; if(clearSel) { for(item = firstItem(); item; item = item->nextItem()) { if(item->isSelected() && item->isVisible()) { item->setSelected(false); changed = true; } } } for(item = from; item; item = item->nextItem()) { if(item->isSelectable()) { if(!invert) { if(!item->isSelected() && item->isVisible()) { item->setSelected(true); changed = true; } } else { bool sel = !item->isSelected(); if((item->isSelected() != sel) && (sel || !sel)) { if(item->isVisible()) { item->setSelected(sel); changed = true; } } } } if(item == to) break; } } void Register::scrollPage(int key, ButtonState state) { RegisterItem* oldFocusItem = m_focusItem; // make sure we have a focus item if(!m_focusItem) setFocusItem(m_firstItem); if(!m_focusItem && m_firstItem) setFocusItem(m_firstItem->nextItem()); if(!m_focusItem) return; RegisterItem* item = m_focusItem; int height = 0; switch(key) { case TQt::Key_PageUp: while(height < visibleHeight() && item->prevItem()) { do { item = item->prevItem(); if(item->isVisible()) height += item->rowHeightHint(); } while((!item->isSelectable() || !item->isVisible()) && item->prevItem()); } break; case TQt::Key_PageDown: while(height < visibleHeight() && item->nextItem()) { do { if(item->isVisible()) height += item->rowHeightHint(); item = item->nextItem(); } while((!item->isSelectable() || !item->isVisible()) && item->nextItem()); } break; case TQt::Key_Up: if(item->prevItem()) { do { item = item->prevItem(); } while((!item->isSelectable() || !item->isVisible()) && item->prevItem()); } break; case TQt::Key_Down: if(item->nextItem()) { do { item = item->nextItem(); } while((!item->isSelectable() || !item->isVisible()) && item->nextItem()); } break; case TQt::Key_Home: item = m_firstItem; while((!item->isSelectable() || !item->isVisible()) && item->nextItem()) item = item->nextItem(); break; case TQt::Key_End: item = m_lastItem; while((!item->isSelectable() || !item->isVisible()) && item->prevItem()) item = item->prevItem(); break; } // make sure to avoid selecting a possible empty transaction at the end Transaction* t = dynamic_cast(item); if(t && t->transaction().id().isEmpty()) { if(t->prevItem()) { item = t->prevItem(); } } if(!(state & ShiftButton) || !m_selectAnchor) m_selectAnchor = item; setFocusItem(item); if(item->isSelectable()) { handleItemChange(oldFocusItem, state & TQt::ShiftButton, state & TQt::ControlButton); // tell the world about the changes in selection SelectedTransactions list(this); emit selectionChanged(list); } if(m_focusItem && !m_focusItem->isSelected() && m_selectionMode == Single) selectItem(item); } void Register::keyPressEvent(TQKeyEvent* ev) { switch(ev->key()) { case TQt::Key_Space: if(m_selectionMode != NoSelection) { // get the state out of the event ... m_buttonState = ev->state(); // ... and pretend that we have pressed the left mouse button ;) m_buttonState = static_cast(m_buttonState | Qt::LeftButton); selectItem(m_focusItem); } break; case TQt::Key_PageUp: case TQt::Key_PageDown: case TQt::Key_Home: case TQt::Key_End: case TQt::Key_Down: case TQt::Key_Up: scrollPage(ev->key(), ev->state()); break; default: TQTable::keyPressEvent(ev); break; } } Transaction* Register::transactionFactory(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) { Transaction* t = 0; MyMoneySplit s = split; if(parent->account() == MyMoneyAccount()) { t = new KMyMoneyRegister::StdTransaction(parent, transaction, s, uniqueId); return t; } switch(parent->account().accountType()) { case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::CreditCard: case MyMoneyAccount::Loan: case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Currency: case MyMoneyAccount::Income: case MyMoneyAccount::Expense: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Equity: if(s.accountId().isEmpty()) s.setAccountId(parent->account().id()); if(s.isMatched()) t = new KMyMoneyRegister::StdTransactionMatched(parent, transaction, s, uniqueId); else if(transaction.isImported()) t = new KMyMoneyRegister::StdTransactionDownloaded(parent, transaction, s, uniqueId); else t = new KMyMoneyRegister::StdTransaction(parent, transaction, s, uniqueId); break; case MyMoneyAccount::Investment: if(s.isMatched()) t = new KMyMoneyRegister::InvestTransaction/* Matched */(parent, transaction, s, uniqueId); else if(transaction.isImported()) t = new KMyMoneyRegister::InvestTransactionDownloaded(parent, transaction, s, uniqueId); else t = new KMyMoneyRegister::InvestTransaction(parent, transaction, s, uniqueId); break; case MyMoneyAccount::CertificateDep: case MyMoneyAccount::MoneyMarket: case MyMoneyAccount::Stock: default: tqDebug("Register::transactionFactory: invalid accountTypeE %d", parent->account().accountType()); break; } return t; } void Register::addGroupMarkers(void) { TQMap list; TQMap::const_iterator it; KMyMoneyRegister::RegisterItem* p = firstItem(); KMyMoneyRegister::Transaction* t; TQString name; TQDate today; TQDate yesterday, thisWeek, lastWeek; TQDate thisMonth, lastMonth; TQDate thisYear; int weekStartOfs; switch(primarySortKey()) { case KMyMoneyRegister::PostDateSort: case KMyMoneyRegister::EntryDateSort: today = TQDate::currentDate(); thisMonth.setYMD(today.year(), today.month(), 1); lastMonth = thisMonth.addMonths(-1); yesterday = today.addDays(-1); // a = TQDate::dayOfWeek() todays weekday (1 = Monday, 7 = Sunday) // b = TDELocale::weekStartDay() first day of week (1 = Monday, 7 = Sunday) weekStartOfs = today.dayOfWeek() - TDEGlobal::locale()->weekStartDay(); if(weekStartOfs < 0) { weekStartOfs = 7 + weekStartOfs; } thisWeek = today.addDays(-weekStartOfs); lastWeek = thisWeek.addDays(-7); thisYear.setYMD(today.year(), 1, 1); if(KMyMoneyGlobalSettings::startDate().date() != TQDate(1900,1,1)) new KMyMoneyRegister::FancyDateGroupMarker(this, KMyMoneyGlobalSettings::startDate().date(), i18n("Prior transactions possibly filtered")); if(KMyMoneyGlobalSettings::showFancyMarker()) { if(m_account.lastReconciliationDate().isValid()) new KMyMoneyRegister::StatementGroupMarker(this, KMyMoneyRegister::Deposit, m_account.lastReconciliationDate(), i18n("Last reconciliation")); if(!m_account.value("lastImportedTransactionDate").isEmpty() && !m_account.value("lastStatementBalance").isEmpty()) { MyMoneyMoney balance(m_account.value("lastStatementBalance")); if(m_account.accountGroup() == MyMoneyAccount::Liability) balance = -balance; TQString txt = i18n("Online Statement Balance: %1").arg(balance.formatMoney(m_account.fraction())); new KMyMoneyRegister::StatementGroupMarker(this, KMyMoneyRegister::Deposit, TQDate::fromString(m_account.value("lastImportedTransactionDate"), Qt::ISODate), txt); } new KMyMoneyRegister::FancyDateGroupMarker(this, thisYear, i18n("This year")); new KMyMoneyRegister::FancyDateGroupMarker(this, lastMonth, i18n("Last month")); new KMyMoneyRegister::FancyDateGroupMarker(this, thisMonth, i18n("This month")); new KMyMoneyRegister::FancyDateGroupMarker(this, lastWeek, i18n("Last week")); new KMyMoneyRegister::FancyDateGroupMarker(this, thisWeek, i18n("This week")); new KMyMoneyRegister::FancyDateGroupMarker(this, yesterday, i18n("Yesterday")); new KMyMoneyRegister::FancyDateGroupMarker(this, today, i18n("Today")); new KMyMoneyRegister::FancyDateGroupMarker(this, today.addDays(1), i18n("Future transactions")); new KMyMoneyRegister::FancyDateGroupMarker(this, thisWeek.addDays(7), i18n("Next week")); new KMyMoneyRegister::FancyDateGroupMarker(this, thisMonth.addMonths(1), i18n("Next month")); } else { new KMyMoneyRegister::SimpleDateGroupMarker(this, today.addDays(1), i18n("Future transactions")); } if(KMyMoneyGlobalSettings::showFiscalMarker()) { TQDate currentFiscalYear(TQDate::currentDate().year(), KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); if(TQDate::currentDate() < currentFiscalYear) currentFiscalYear = currentFiscalYear.addYears(-1); TQDate previousFiscalYear = currentFiscalYear.addYears(-1); new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear, i18n("Current fiscal year")); new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear.addYears(-1), i18n("Previous fiscal year")); new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear.addYears(1), i18n("Next fiscal year")); } break; case KMyMoneyRegister::TypeSort: if(KMyMoneyGlobalSettings::showFancyMarker()) { new KMyMoneyRegister::TypeGroupMarker(this, KMyMoneyRegister::Deposit, m_account.accountType()); new KMyMoneyRegister::TypeGroupMarker(this, KMyMoneyRegister::Payment, m_account.accountType()); } break; case KMyMoneyRegister::ReconcileStateSort: if(KMyMoneyGlobalSettings::showFancyMarker()) { new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::NotReconciled); new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::Cleared); new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::Reconciled); new KMyMoneyRegister::ReconcileGroupMarker(this, MyMoneySplit::Frozen); } break; case KMyMoneyRegister::PayeeSort: if(KMyMoneyGlobalSettings::showFancyMarker()) { while(p) { t = dynamic_cast(p); if(t) { list[t->sortPayee()] = 1; } p = p->nextItem(); } for(it = list.begin(); it != list.end(); ++it) { name = it.key(); if(name.isEmpty()) { name = i18n("Unknown payee", "Unknown"); } new KMyMoneyRegister::PayeeGroupMarker(this, name); } } break; case KMyMoneyRegister::CategorySort: if(KMyMoneyGlobalSettings::showFancyMarker()) { while(p) { t = dynamic_cast(p); if(t) { list[t->sortCategory()] = 1; } p = p->nextItem(); } for(it = list.begin(); it != list.end(); ++it) { name = it.key(); if(name.isEmpty()) { name = i18n("Unknown category", "Unknown"); } new KMyMoneyRegister::CategoryGroupMarker(this, name); } } break; case KMyMoneyRegister::SecuritySort: if(KMyMoneyGlobalSettings::showFancyMarker()) { while(p) { t = dynamic_cast(p); if(t) { list[t->sortSecurity()] = 1; } p = p->nextItem(); } for(it = list.begin(); it != list.end(); ++it) { name = it.key(); if(name.isEmpty()) { name = i18n("Unknown security", "Unknown"); } new KMyMoneyRegister::CategoryGroupMarker(this, name); } } break; default: // no markers supported break; } } void Register::removeUnwantedGroupMarkers(void) { // remove all trailing group markers except statement markers KMyMoneyRegister::RegisterItem* q; KMyMoneyRegister::RegisterItem* p = lastItem(); while(p) { q = p; if(dynamic_cast(p) || dynamic_cast(p)) break; p = p->prevItem(); delete q; } // remove all adjacent group markers bool lastWasGroupMarker = false; p = lastItem(); while(p) { q = p; KMyMoneyRegister::GroupMarker* m = dynamic_cast(p); p = p->prevItem(); if(m) { m->markVisible(true); // make adjacent group marker invisible except those that show statement information if(lastWasGroupMarker && (dynamic_cast(m) == 0)) { m->markVisible(false); } lastWasGroupMarker = true; } else if(q->isVisible()) lastWasGroupMarker = false; } } #include "register.moc"