diff options
Diffstat (limited to 'src/widgets/ingredientinputwidget.cpp')
-rw-r--r-- | src/widgets/ingredientinputwidget.cpp | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/src/widgets/ingredientinputwidget.cpp b/src/widgets/ingredientinputwidget.cpp new file mode 100644 index 0000000..3c0aa7e --- /dev/null +++ b/src/widgets/ingredientinputwidget.cpp @@ -0,0 +1,542 @@ +/*************************************************************************** +* Copyright (C) 2003-2005 by * +* Unai Garro (ugarro@users.sourceforge.net) * +* Cyril Bosselut (bosselut@b1project.com) * +* Jason Kivlighn (jkivlighn@gmail.com) * +* * +* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.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. * +***************************************************************************/ + +#include "ingredientinputwidget.h" + +#include <tqlabel.h> +#include <tqwidgetstack.h> +#include <tqhbox.h> +#include <tqvbox.h> +#include <tqgroupbox.h> +#include <tqbuttongroup.h> +#include <tqradiobutton.h> +#include <tqcheckbox.h> + +#include <kcombobox.h> +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <kdebug.h> + +#include "backends/recipedb.h" +#include "datablocks/unit.h" +#include "widgets/fractioninput.h" +#include "widgets/ingredientcombobox.h" +#include "widgets/headercombobox.h" +#include "widgets/prepmethodcombobox.h" +#include "dialogs/createunitdialog.h" + +#include "profiling.h" + +IngredientInput::IngredientInput( RecipeDB *db, TQWidget *parent, bool allowHeader ) : TQHBox(parent), database(db) +{ + TQVBox *ingredientVBox = new TQVBox( this ); + TQHBox *typeHBox = new TQHBox( ingredientVBox ); + + if ( allowHeader ) { + typeButtonGrp = new TQButtonGroup(); + TQRadioButton *ingredientRadioButton = new TQRadioButton( i18n( "Ingredient:" ), typeHBox ); + typeButtonGrp->insert( ingredientRadioButton, 0 ); + + TQRadioButton *headerRadioButton = new TQRadioButton( i18n( "Ingredient grouping name", "Header:" ), typeHBox ); + typeButtonGrp->insert( headerRadioButton, 1 ); + + typeButtonGrp->setButton( 0 ); + connect( typeButtonGrp, TQ_SIGNAL( clicked( int ) ), TQ_SLOT( typeButtonClicked( int ) ) ); + } + else { + (void) new TQLabel( i18n( "Ingredient:" ), typeHBox ); + typeButtonGrp = 0; + } + + header_ing_stack = new TQWidgetStack(ingredientVBox); + ingredientBox = new IngredientComboBox( TRUE, header_ing_stack, database ); + ingredientBox->setAutoCompletion( TRUE ); + ingredientBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box + ingredientBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + header_ing_stack->addWidget( ingredientBox ); + headerBox = new HeaderComboBox( TRUE, header_ing_stack, database ); + headerBox->setAutoCompletion( TRUE ); + headerBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box + headerBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + header_ing_stack->addWidget( headerBox ); + + TQVBox *amountVBox = new TQVBox( this ); + amountLabel = new TQLabel( i18n( "Amount:" ), amountVBox ); + amountEdit = new FractionInput( amountVBox ); + amountEdit->setAllowRange(true); + amountEdit->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Fixed ) ); + + TQVBox *unitVBox = new TQVBox( this ); + unitLabel = new TQLabel( i18n( "Unit:" ), unitVBox ); + unitBox = new KComboBox( TRUE, unitVBox ); + unitBox->setAutoCompletion( TRUE ); + unitBox->lineEdit() ->disconnect( unitBox ); //so hitting enter doesn't enter the item into the box + unitBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + + TQVBox *prepMethodVBox = new TQVBox( this ); + prepMethodLabel = new TQLabel( i18n( "Preparation Method:" ), prepMethodVBox ); + prepMethodBox = new PrepMethodComboBox( TRUE, prepMethodVBox, database ); + prepMethodBox->setAutoCompletion( TRUE ); + prepMethodBox->lineEdit() ->disconnect( prepMethodBox ); //so hitting enter doesn't enter the item into the box + prepMethodBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + + orButton = new TQCheckBox( i18n( "OR" ), this ); + + setStretchFactor( ingredientVBox, 5 ); + setStretchFactor( amountVBox, 1 ); + setStretchFactor( unitVBox, 2 ); + setStretchFactor( prepMethodVBox, 3 ); + + connect( ingredientBox, TQ_SIGNAL( activated( int ) ), this, TQ_SLOT( loadUnitListCombo() ) ); + connect( ingredientBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotIngredientBoxLostFocus() ) ); + connect( unitBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotUnitBoxLostFocus() ) ); + connect( prepMethodBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotPrepMethodBoxLostFocus() ) ); + connect( orButton, TQ_SIGNAL( toggled(bool) ), this, TQ_SLOT( orToggled(bool) ) ); + + connect( unitBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( ingredientBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( headerBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( prepMethodBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( amountEdit, TQ_SIGNAL( returnPressed( const TQString & ) ), this, TQ_SLOT( signalIngredient() ) ); + + unitComboList = new UnitList; + + setFocusProxy( ingredientBox ); +} + +IngredientInput::~IngredientInput() +{ + delete unitComboList; + delete typeButtonGrp; +} + +void IngredientInput::clear() +{ + unitComboList->clear(); + + orButton->setChecked(false); + typeButtonGrp->setButton( 0 ); //put back to ingredient input + typeButtonClicked( 0 ); + + amountEdit->clear(); + ingredientBox->lineEdit()->setText(""); + prepMethodBox->lineEdit()->setText(""); + headerBox->lineEdit()->setText(""); + unitBox->lineEdit()->setText(""); +} + +void IngredientInput::orToggled(bool b) +{ + emit orToggled(b,this); +} + +void IngredientInput::reloadCombos() +{ + //these only needed to be loaded once + if ( ingredientBox->count() == 0 ) { + START_TIMER("Loading ingredient input auto-completion"); + ingredientBox->reload(); + END_TIMER(); + } + if ( headerBox->count() == 0 ) { + START_TIMER("Loading ingredient header input auto-completion"); + headerBox->reload(); + END_TIMER(); + } + if ( prepMethodBox->count() == 0 ) { + START_TIMER("Loading prep method input auto-completion"); + prepMethodBox->reload(); + END_TIMER(); + } + + loadUnitListCombo(); +} + +void IngredientInput::slotIngredientBoxLostFocus( void ) +{ + if ( ingredientBox->contains( ingredientBox->currentText() ) ) { + ingredientBox->setCurrentItem( ingredientBox->currentText() ); + loadUnitListCombo(); + } + else { + unitBox->clear(); + unitBox->completionObject() ->clear(); + unitComboList->clear(); + } +} + +void IngredientInput::slotUnitBoxLostFocus() +{ + if ( unitBox->contains( unitBox->currentText() ) ) + unitBox->setCurrentItem( unitBox->currentText() ); +} + +void IngredientInput::slotPrepMethodBoxLostFocus() +{ + if ( prepMethodBox->contains( prepMethodBox->currentText() ) ) + prepMethodBox->setCurrentItem( prepMethodBox->currentText() ); +} + +void IngredientInput::typeButtonClicked( int button_id ) +{ + if ( amountEdit->isEnabled() == !bool( button_id ) ) //it is already set (the same button was clicked more than once) + return ; + + amountEdit->setEnabled( !bool( button_id ) ); + unitBox->setEnabled( !bool( button_id ) ); + prepMethodBox->setEnabled( !bool( button_id ) ); + + if ( button_id == 1 ) { //Header + header_ing_stack->raiseWidget( headerBox ); + } + else { + header_ing_stack->raiseWidget( ingredientBox ); + } +} + +void IngredientInput::enableHeader( bool enable ) +{ + if ( !enable ) { + typeButtonGrp->setButton( 0 ); //put back to ingredient input + typeButtonClicked( 0 ); + } + typeButtonGrp->find(1)->setEnabled(enable); +} + +void IngredientInput::signalIngredient() +{ + //validate input; if successful, emit signal + if ( isHeader() ) { + if ( header().isEmpty() ) + return; + } + else { + if ( !isInputValid() ) + return; + } + + emit addIngredient(); +} + +bool IngredientInput::isInputValid() +{ + if ( ingredientBox->currentText().stripWhiteSpace().isEmpty() ) { + KMessageBox::error( this, i18n( "Please enter an ingredient" ), TQString::null ); + ingredientBox->setFocus(); + return false; + } + return checkAmountEdit() && checkBounds(); +} + +bool IngredientInput::checkBounds() +{ + if ( ingredientBox->currentText().length() > uint(database->maxIngredientNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Ingredient name cannot be longer than %1 characters." ) ).arg( database->maxIngredientNameLength() ) ); + ingredientBox->lineEdit() ->setFocus(); + ingredientBox->lineEdit() ->selectAll(); + return false; + } + + if ( unitBox->currentText().length() > uint(database->maxUnitNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) ); + unitBox->lineEdit() ->setFocus(); + unitBox->lineEdit() ->selectAll(); + return false; + } + + TQStringList prepMethodList = TQStringList::split(",",prepMethodBox->currentText()); + for ( TQStringList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it ) { + if ( (*it).stripWhiteSpace().length() > uint(database->maxPrepMethodNameLength()) ) + { + KMessageBox::error( this, TQString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) ); + prepMethodBox->lineEdit() ->setFocus(); + prepMethodBox->lineEdit() ->selectAll(); + return false; + } + } + + return true; +} + +bool IngredientInput::checkAmountEdit() +{ + if ( amountEdit->isInputValid() ) + return true; + else { + KMessageBox::error( this, i18n( "Amount field contains invalid input." ), + i18n( "Invalid input" ) ); + amountEdit->setFocus(); + amountEdit->selectAll(); + return false; + } +} + +void IngredientInput::loadUnitListCombo() +{ + TQString store_unit = unitBox->currentText(); + unitBox->clear(); // Empty the combo first + unitBox->completionObject() ->clear(); + + int comboIndex = ingredientBox->currentItem(); + int comboCount = ingredientBox->count(); + + if ( comboCount > 0 ) { // If not, the list may be empty (no ingredient list defined) and crashes while reading + int selectedIngredient = ingredientBox->id( comboIndex ); + database->loadPossibleUnits( selectedIngredient, unitComboList ); + + //Populate this data into the ComboBox + for ( UnitList::const_iterator unit_it = unitComboList->begin(); unit_it != unitComboList->end(); ++unit_it ) { + unitBox->insertItem( ( *unit_it ).name ); + unitBox->completionObject() ->addItem( ( *unit_it ).name ); + if ( ( *unit_it ).name != (*unit_it ).plural ) { + unitBox->insertItem( ( *unit_it ).plural ); + unitBox->completionObject() ->addItem( ( *unit_it ).plural ); + } + + if ( !( *unit_it ).name_abbrev.isEmpty() ) { + unitBox->insertItem( ( *unit_it ).name_abbrev ); + unitBox->completionObject() ->addItem( ( *unit_it ).name_abbrev ); + } + if ( !(*unit_it ).plural_abbrev.isEmpty() && + ( *unit_it ).name_abbrev != (*unit_it ).plural_abbrev ) { + unitBox->insertItem( ( *unit_it ).plural_abbrev ); + unitBox->completionObject() ->addItem( ( *unit_it ).plural_abbrev ); + } + + } + } + unitBox->lineEdit() ->setText( store_unit ); +} + +bool IngredientInput::isHeader() const +{ + return typeButtonGrp && (typeButtonGrp->id( typeButtonGrp->selected() ) == 1); +} + +Ingredient IngredientInput::ingredient() const +{ + Ingredient ing; + + ing.prepMethodList = ElementList::split(",",prepMethodBox->currentText()); + ing.name = ingredientBox->currentText(); + amountEdit->value(ing.amount,ing.amount_offset); + ing.units = Unit(unitBox->currentText().stripWhiteSpace(),ing.amount+ing.amount_offset); + ing.ingredientID = ingredientBox->id( ingredientBox->currentItem() ); + + return ing; +} + +TQString IngredientInput::header() const +{ + return headerBox->currentText().stripWhiteSpace(); +} + +void IngredientInput::updateTabOrder() +{ + TQWidget::setTabOrder( ingredientBox, amountEdit ); + TQWidget::setTabOrder( amountEdit, unitBox ); + TQWidget::setTabOrder( unitBox, prepMethodBox ); + TQWidget::setTabOrder( prepMethodBox, orButton ); +} + + +IngredientInputWidget::IngredientInputWidget( RecipeDB *db, TQWidget *parent ) : TQVBox(parent), database(db) +{ + setSizePolicy( TQSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Fixed ) ); + + m_ingInputs.append(new IngredientInput(database,this)); + + // Connect signals & Slots + connect( m_ingInputs[0], TQ_SIGNAL(addIngredient()), this, TQ_SLOT(addIngredient()) ); + connect( m_ingInputs[0], TQ_SIGNAL(orToggled(bool,IngredientInput*)), this, TQ_SLOT(updateInputs(bool,IngredientInput*)) ); + + reloadCombos(); +} + +IngredientInputWidget::~IngredientInputWidget() +{ +} + +void IngredientInputWidget::clear() +{ + //clearing the first input deletes all substitute inputs + m_ingInputs[0]->clear(); +} + +void IngredientInputWidget::updateInputs(bool on, IngredientInput* input) +{ + TQValueList<IngredientInput*>::iterator curr = m_ingInputs.find(input); + IngredientInput *prev_input = *curr; + ++curr; + + if ( on ) { + IngredientInput *new_input = new IngredientInput(database,this,false); + new_input->reloadCombos(); + + TQWidget::setTabOrder( prev_input, new_input ); + new_input->updateTabOrder(); + + connect( new_input, TQ_SIGNAL(addIngredient()), this, TQ_SLOT(addIngredient()) ); + connect( new_input, TQ_SIGNAL(orToggled(bool,IngredientInput*)), this, TQ_SLOT(updateInputs(bool,IngredientInput*)) ); + + new_input->show(); + m_ingInputs.insert(curr,new_input); + + m_ingInputs[0]->enableHeader(false); + + } + else { + while ( curr != m_ingInputs.end() ) { + (*curr)->deleteLater(); + curr = m_ingInputs.remove(curr); + } + if ( m_ingInputs.count() == 1 ) + m_ingInputs[0]->enableHeader(true); + } +} + +void IngredientInputWidget::addIngredient() +{ + if ( m_ingInputs[0]->isHeader() ) { + TQString header = m_ingInputs[0]->header(); + if ( header.isEmpty() ) + return; + + int group_id = createNewGroupIfNecessary( header,database ); + emit headerEntered( Element(header,group_id) ); + } + else { + for ( TQValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) { + if ( !(*it)->isInputValid() ) + return; + } + + TQValueList<IngredientData> list; + for ( TQValueList<IngredientInput*>::const_iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) { + Ingredient ing = (*it)->ingredient(); + ing.ingredientID = createNewIngredientIfNecessary(ing.name,database); + + bool plural = ing.amount+ing.amount_offset > 1; + ing.units.id = createNewUnitIfNecessary( (plural)?ing.units.plural:ing.units.name, plural, ing.ingredientID, ing.units,database ); + if ( ing.units.id == -1 ) // this will happen if the dialog to create a unit was cancelled + return ; + + TQValueList<int> prepIDs = createNewPrepIfNecessary( ing.prepMethodList,database ); + TQValueList<int>::const_iterator id_it = prepIDs.begin(); + for ( ElementList::iterator it = ing.prepMethodList.begin(); it != ing.prepMethodList.end(); ++it, ++id_it ) { + (*it).id = *id_it; + } + + list.append(ing); + } + + Ingredient ing = list.first(); + list.pop_front(); + ing.substitutes = list; + emit ingredientEntered( ing ); + } + clear(); + + m_ingInputs[0]->setFocus(); //put cursor back to the ingredient name so user can begin next ingredient +} + +int IngredientInputWidget::createNewIngredientIfNecessary( const TQString &ing, RecipeDB *database ) +{ + int id = -1; + if ( ing.isEmpty() ) + return -1; + + id = database->findExistingIngredientByName( ing ); + if ( id == -1 ) { + database->createNewIngredient( ing ); + id = database->lastInsertID(); + } + return id; +} + +int IngredientInputWidget::createNewUnitIfNecessary( const TQString &unit, bool plural, int ingredientID, Unit &new_unit, RecipeDB *database ) +{ + int id = database->findExistingUnitByName( unit ); + if ( -1 == id ) + { + CreateUnitDialog getUnit( 0, ( plural ) ? TQString::null : unit, ( !plural ) ? TQString::null : unit ); + if ( getUnit.exec() == TQDialog::Accepted ) { + new_unit = getUnit.newUnit(); + database->createNewUnit( new_unit ); + + id = database->lastInsertID(); + } + } + + if ( !database->ingredientContainsUnit( + ingredientID, + id ) ) + { + database->addUnitToIngredient( + ingredientID, + id ); + } + + new_unit = database->unitName( id ); + + //loadUnitListCombo(); + return id; +} + +TQValueList<int> IngredientInputWidget::createNewPrepIfNecessary( const ElementList &prepMethods, RecipeDB *database ) +{ + TQValueList<int> ids; + + if ( prepMethods.isEmpty() ) //no prep methods + return ids; + else + { + for ( ElementList::const_iterator it = prepMethods.begin(); it != prepMethods.end(); ++it ) { + int id = database->findExistingPrepByName( (*it).name.stripWhiteSpace() ); + if ( id == -1 ) + { + database->createNewPrepMethod( (*it).name.stripWhiteSpace() ); + id = database->lastInsertID(); + } + ids << id; + } + + return ids; + } +} + +int IngredientInputWidget::createNewGroupIfNecessary( const TQString &group, RecipeDB *database ) +{ + if ( group.stripWhiteSpace().isEmpty() ) //no group + return -1; + else + { + int id = database->findExistingIngredientGroupByName( group ); + if ( id == -1 ) //creating new + { + database->createNewIngGroup( group ); + id = database->lastInsertID(); + } + + return id; + } +} + +void IngredientInputWidget::reloadCombos() +{ + for ( TQValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) + (*it)->reloadCombos(); +} + +#include "ingredientinputwidget.moc" |