/*************************************************************************** kmymoneyedit.cpp ------------------- copyright : (C) 2000 by Michael Edwardes, 2004 by Thomas Baumgart email : mte@users.sourceforge.net ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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 // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "kmymoneyedit.h" #include "kmymoneycalculator.h" #include "../mymoney/mymoneymoney.h" kMyMoneyMoneyValidator::kMyMoneyMoneyValidator(TQObject * parent, const char * name) : TQDoubleValidator(parent, name) { } kMyMoneyMoneyValidator::kMyMoneyMoneyValidator( double bottom, double top, int decimals, TQObject * parent, const char * name ) : TQDoubleValidator(bottom, top, decimals, parent, name) { } /* * The code of the following function is taken from tdeui/knumvalidator.cpp * and adjusted to always use the monetary symbols defined in the KDE control center */ TQValidator::State kMyMoneyMoneyValidator::validate( TQString & input, int & _p ) const { TQString s = input; TDELocale * l = TDEGlobal::locale(); // ok, we have to re-format the number to have: // 1. decimalSymbol == '.' // 2. negativeSign == '-' // 3. positiveSign == // 4. thousandsSeparator() == (we don't check that there // are exactly three decimals between each separator): TQString d = l->monetaryDecimalSymbol(), n = l->negativeSign(), p = l->positiveSign(), t = l->monetaryThousandsSeparator(); // first, delete p's and t's: if ( !p.isEmpty() ) for ( int idx = s.find( p ) ; idx >= 0 ; idx = s.find( p, idx ) ) s.remove( idx, p.length() ); if ( !t.isEmpty() ) for ( int idx = s.find( t ) ; idx >= 0 ; idx = s.find( t, idx ) ) s.remove( idx, t.length() ); // then, replace the d's and n's if ( ( !n.isEmpty() && n.find('.') != -1 ) || ( !d.isEmpty() && d.find('-') != -1 ) ) { // make sure we don't replace something twice: kdWarning() << "KDoubleValidator: decimal symbol contains '-' or " "negative sign contains '.' -> improve algorithm" << endl; return Invalid; } if ( !d.isEmpty() && d != "." ) for ( int idx = s.find( d ) ; idx >= 0 ; idx = s.find( d, idx + 1 ) ) s.replace( idx, d.length(), "."); if ( !n.isEmpty() && n != "-" ) for ( int idx = s.find( n ) ; idx >= 0 ; idx = s.find( n, idx + 1 ) ) s.replace( idx, n.length(), "-" ); // Take care of monetary parens around the value if selected via // the locale settings. // If the lead-in or lead-out paren is present, remove it // before passing the string to the TQDoubleValidator if(l->negativeMonetarySignPosition() == TDELocale::ParensAround || l->positiveMonetarySignPosition() == TDELocale::ParensAround) { TQRegExp regExp("^(\\()?([\\d-\\.]*)(\\))?$"); if(s.find(regExp) != -1) { s = regExp.cap(2); } } // check for non numeric values (TQDoubleValidator allows an 'e', we don't) TQRegExp nonNumeric("[^\\d-\\.]+"); if(s.find(nonNumeric) != -1) return Invalid; // check for minus sign trailing the number TQRegExp trailingMinus("^([^-]*)\\w*-$"); if(s.find(trailingMinus) != -1) { s = TQString("-%1").arg(trailingMinus.cap(1)); } // check for the maximum allowed number of decimal places int decPos = s.find('.'); if(decPos != -1) { if(decimals() == 0) return Invalid; if(((int)(s.length()) - decPos) > decimals()) return Invalid; } // If we have just a single minus sign, we are done if(s == TQString("-")) return Acceptable; TQValidator::State rc = TQDoubleValidator::validate( s, _p ); if(rc == Acceptable) { // If the numeric value is acceptable, we check if the parens // are ok. If only the lead-in is present, the return value // is intermediate, if only the lead-out is present then it // definitely is invalid. Nevertheless, we check for parens // only, if the locale settings have it enabled. if(l->negativeMonetarySignPosition() == TDELocale::ParensAround || l->positiveMonetarySignPosition() == TDELocale::ParensAround) { int tmp = input.contains('(') - input.contains(')'); if(tmp > 0) rc = Intermediate; else if(tmp < 0) rc = Invalid; } } return rc; } kMyMoneyEdit::kMyMoneyEdit(TQWidget *parent, const char *name, const int prec) : TQHBox(parent, name) { m_prec = prec; if(prec < -1 || prec > 20) m_prec = TDEGlobal::locale()->fracDigits(); init(); } kMyMoneyEdit::kMyMoneyEdit(const MyMoneySecurity& sec, TQWidget *parent, const char *name) : TQHBox(parent, name) { m_prec = MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()); init(); } // converted image from trinity.5.1/share/apps/kdevdesignerpart/pics/designer_resetproperty.png static const uchar resetButtonImage[] = { 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A, 0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52, 0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x06, 0x08,0x06,0x00,0x00,0x00,0x0F,0x0E,0x84, 0x76,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,0x07,0x74,0x49,0x4D,0x45, 0x07,0xD6,0x06,0x10,0x09,0x36,0x0C,0x58, 0x91,0x11,0x7C,0x00,0x00,0x00,0x64,0x49, 0x44,0x41,0x54,0x78,0xDA,0x65,0xC9,0xA1, 0x0D,0x02,0x41,0x18,0x84,0xD1,0xF7,0x5F, 0x13,0x04,0x9A,0x39,0x43,0x68,0x81,0x02, 0x10,0xB8,0x13,0x74,0x80,0xC1,0x21,0x76, 0x1D,0xDD,0xD0,0x01,0x65,0x10,0x34,0x9A, 0x0C,0x66,0x83,0x61,0x92,0x2F,0x23,0x5E, 0x25,0x01,0xBD,0x6A,0xC6,0x1D,0x9B,0x25, 0x79,0xC2,0x34,0xE0,0x30,0x00,0x56,0xBD, 0x6A,0x0D,0xD5,0x38,0xE1,0xEA,0x7F,0xE7, 0x4A,0xA2,0x57,0x1D,0x71,0xC1,0x07,0xBB, 0x81,0x8F,0x09,0x96,0xE4,0x86,0x3D,0xDE, 0x78,0x8D,0x48,0xF2,0xAB,0xB1,0x1D,0x9F, 0xC6,0xFC,0x05,0x46,0x68,0x28,0x6B,0x58, 0xEE,0x72,0x0A,0x00,0x00,0x00,0x00,0x49, 0x45,0x4E,0x44,0xAE,0x42,0x60,0x82 }; void kMyMoneyEdit::init(void) { allowEmpty = false; m_edit = new kMyMoneyLineEdit(this, 0, true); m_edit->installEventFilter(this); setFocusProxy(m_edit); // Yes, just a simple double validator ! kMyMoneyMoneyValidator *validator = new kMyMoneyMoneyValidator(this); m_edit->setValidator(validator); m_edit->setAlignment(AlignRight | AlignVCenter); m_calculatorFrame = new TQVBox(this, 0, WType_Popup); m_calculatorFrame->setFrameStyle(TQFrame::PopupPanel | TQFrame::Raised); m_calculatorFrame->setLineWidth(3); m_calculator = new kMyMoneyCalculator(m_calculatorFrame); m_calculatorFrame->setFixedSize(m_calculator->width()+3, m_calculator->height()+3); m_calculatorFrame->hide(); m_calcButton = new KPushButton(TQIconSet(TQPixmap(TDEGlobal::iconLoader()->iconPath("kcalc", -TDEIcon::SizeSmall))), TQString(""), this); m_calcButton->setFixedWidth( m_calcButton->sizeHint().width() ); m_calcButton->setFixedHeight(m_edit->sizeHint().height()); m_calcButton->setFocusProxy(m_edit); TQPixmap pixmap; pixmap.loadFromData(resetButtonImage, sizeof(resetButtonImage), "PNG", 0); m_resetButton = new KPushButton(TQIconSet(pixmap), TQString(""), this); m_resetButton->setFixedWidth( m_resetButton->sizeHint().width() ); m_resetButton->setFixedHeight(m_edit->sizeHint().height()); m_resetButton->setEnabled(false); m_resetButton->setFocusProxy(m_edit); TDEConfig *tdeconfig = TDEGlobal::config(); tdeconfig->setGroup("General Options"); if(tdeconfig->readBoolEntry("DontShowCalculatorButton", false) == true) setCalculatorButtonVisible(false); setSpacing(0); connect(m_edit, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(theTextChanged(const TQString&))); connect(m_calculator, TQT_SIGNAL(signalResultAvailable()), this, TQT_SLOT(slotCalculatorResult())); connect(m_calcButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotCalculatorOpen())); connect(m_resetButton, TQT_SIGNAL(clicked()), this, TQT_SLOT(resetText())); } void kMyMoneyEdit::setValidator(const TQValidator* v) { m_edit->setValidator(v); } kMyMoneyEdit::~kMyMoneyEdit() { delete m_calculatorFrame; } KLineEdit* kMyMoneyEdit::lineedit(void) const { return m_edit; } void kMyMoneyEdit::setPrecision(const int prec) { if(prec >= -1 && prec <= 20) { if(prec != m_prec) { m_prec = prec; // update current display setValue(value()); } } } bool kMyMoneyEdit::isValid(void) const { return !(m_edit->text().isEmpty()); } MyMoneyMoney kMyMoneyEdit::value(void) const { TQString txt = m_edit->text(); ensureFractionalPart(txt); MyMoneyMoney money(txt); if(m_prec != -1) money = money.convert(MyMoneyMoney::precToDenom(m_prec)); return money; } void kMyMoneyEdit::setValue(const MyMoneyMoney& value) { // load the value into the widget but don't use thousandsSeparators TQString txt = value.formatMoney("", m_prec, false); loadText(txt); } void kMyMoneyEdit::loadText(const TQString& txt) { m_edit->setText(txt); if(isEnabled() && !txt.isEmpty()) ensureFractionalPart(); m_text = m_edit->text(); m_resetButton->setEnabled(false); } void kMyMoneyEdit::clearText(void) { m_text = TQString(); m_edit->setText(m_text); } void kMyMoneyEdit::resetText(void) { m_edit->setText(m_text); m_resetButton->setEnabled(false); } void kMyMoneyEdit::theTextChanged(const TQString & theText) { TDELocale * l = TDEGlobal::locale(); TQString d = l->monetaryDecimalSymbol(); TQString l_text = theText; TQString nsign, psign; if(l->negativeMonetarySignPosition() == TDELocale::ParensAround || l->positiveMonetarySignPosition() == TDELocale::ParensAround) { nsign = psign = "("; } else { nsign = l->negativeSign(); psign = l->positiveSign(); } int i = 0; if(isEnabled()) { TQValidator::State state = m_edit->validator()->validate( l_text, i); if(state == TQValidator::Intermediate) { if(l_text.length() == 1) { if(l_text != d && l_text != nsign && l_text != psign && l_text != "-") state = TQValidator::Invalid; } } if (state==TQValidator::Invalid) m_edit->setText(previousText); else { previousText = l_text; emit textChanged(m_edit->text()); m_resetButton->setEnabled(true); } } } void kMyMoneyEdit::ensureFractionalPart(void) { TQString s(m_edit->text()); ensureFractionalPart(s); m_edit->setText(s); } void kMyMoneyEdit::ensureFractionalPart(TQString& s) const { TDELocale* locale = TDEGlobal::locale(); TQString decimalSymbol = locale->monetaryDecimalSymbol(); if(decimalSymbol.isEmpty()) decimalSymbol = "."; // If text contains no 'monetaryDecimalSymbol' then add it // followed by the required number of 0s if (!s.isEmpty()) { if(m_prec > 0) { if (!s.contains(decimalSymbol)) { s += decimalSymbol; for (int i=0; i < m_prec; i++) s += "0"; } } else if(m_prec == 0) { while(s.contains(decimalSymbol)) { int pos = s.findRev(decimalSymbol); if(pos != -1) { s.truncate(pos); } } } else if(s.contains(decimalSymbol)) { // m_prec == -1 && fraction // no trailing zeroes while(s.endsWith("0")) { s.truncate(s.length()-1); } // no trailing decimalSymbol if(s.endsWith(decimalSymbol)) s.truncate(s.length()-1); } } } bool kMyMoneyEdit::eventFilter(TQObject * /* o */ , TQEvent *e ) { bool rc = false; // we want to catch some keys that are usually handled by // the base class (e.g. '+', '-', etc.) if(e->type() == TQEvent::KeyPress) { TQKeyEvent *k = static_cast (e); rc = true; switch(k->key()) { case TQt::Key_Plus: case TQt::Key_Minus: if(m_edit->hasSelectedText()) { m_edit->cut(); } if(m_edit->text().length() == 0) { rc = false; break; } // in case of '-' we do not enter the calculator when // the current position is the beginning and there is // no '-' sign at the first position. if(k->key() == TQt::Key_Minus) { if(m_edit->cursorPosition() == 0 && m_edit->text()[0] != '-') { rc = false; break; } } // otherwise, tricky fall through here! case TQt::Key_Slash: case TQt::Key_Asterisk: case TQt::Key_Percent: if(m_edit->hasSelectedText()) { // remove the selected text m_edit->cut(); } calculatorOpen(k); break; default: rc = false; break; } } else if(e->type() == TQEvent::FocusOut) { if(!m_edit->text().isEmpty() || !allowEmpty) ensureFractionalPart(); if(MyMoneyMoney(m_edit->text()) != MyMoneyMoney(m_text) && !m_calculator->isVisible()) { emit valueChanged(m_edit->text()); } m_text = m_edit->text(); } return rc; } void kMyMoneyEdit::slotCalculatorOpen(void) { calculatorOpen(0); } void kMyMoneyEdit::calculatorOpen(TQKeyEvent* k) { m_calculator->setInitialValues(m_edit->text(), k); int h = m_calculatorFrame->height(); int w = m_calculatorFrame->width(); // usually, the calculator widget is shown underneath the MoneyEdit widget // if it does not fit on the screen, we show it above this widget TQPoint p = mapToGlobal(TQPoint(0,0)); if(p.y() + height() + h > TQApplication::desktop()->height()) p.setY(p.y() - h); else p.setY(p.y() + height()); // usually, it is shown left aligned. If it does not fit, we align it // to the right edge of the widget if(p.x() + w > TQApplication::desktop()->width()) p.setX(p.x() + width() - w); TQRect r = m_calculator->geometry(); r.moveTopLeft(p); m_calculatorFrame->setGeometry(r); m_calculatorFrame->show(); m_calculator->setFocus(); } void kMyMoneyEdit::slotCalculatorResult(void) { TQString result; if(m_calculator != 0) { m_calculatorFrame->hide(); m_edit->setText(m_calculator->result()); ensureFractionalPart(); emit valueChanged(m_edit->text()); m_text = m_edit->text(); } } TQWidget* kMyMoneyEdit::focusWidget(void) const { TQWidget* w = m_edit; while(w->focusProxy()) w = w->focusProxy(); return w; } void kMyMoneyEdit::setCalculatorButtonVisible(const bool show) { m_calcButton->setShown(show); } void kMyMoneyEdit::setResetButtonVisible(const bool show) { m_resetButton->setShown(show); } void kMyMoneyEdit::setAllowEmpty(bool allowed) { allowEmpty = allowed; } bool kMyMoneyEdit::isCalculatorButtonVisible(void) const { return m_calcButton->isVisible(); } bool kMyMoneyEdit::isResetButtonVisible(void) const { return m_resetButton->isVisible(); } bool kMyMoneyEdit::isEmptyAllowed(void) const { return allowEmpty; } void kMyMoneyEdit::setHint(const TQString& hint) const { if(m_edit) m_edit->setHint(hint); } bool kMyMoneyEdit::isReadOnly(void) const { if(m_edit) return m_edit->isReadOnly(); return false; } void kMyMoneyEdit::setReadOnly(bool readOnly) { // we use the TQLineEdit::setReadOnly() method directly to avoid // changing the background between readonly and read/write mode // as it is done by the KLineEdit code. if(m_edit) m_edit->TQLineEdit::setReadOnly(readOnly); } #include "kmymoneyedit.moc"