You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/widgets/kmymoneyedit.cpp

564 lines
16 KiB

/***************************************************************************
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 <config.h>
#endif
// ----------------------------------------------------------------------------
// QT Includes
#include <tqapplication.h>
#include <tqdesktopwidget.h>
#include <tqwidget.h>
#include <tqvbox.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <tdeglobal.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <klineedit.h>
#include <kiconloader.h>
#include <tdeconfig.h>
// ----------------------------------------------------------------------------
// Project Includes
#include <kmymoney/kmymoneylineedit.h>
#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 == <empty>
// 4. thousandsSeparator() == <empty> (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<TQKeyEvent*> (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"