/*************************************************************************** * Copyright (C) 2003 by * * Unai Garro (ugarro@users.sourceforge.net) * * Cyril Bosselut (bosselut@b1project.com) * * 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 "dietwizarddialog.h" #include "backends/recipedb.h" #include "dietviewdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "propertycalculator.h" #include "widgets/propertylistview.h" #include "widgets/categorylistview.h" #include "profiling.h" DietWizardDialog::DietWizardDialog( TQWidget *parent, RecipeDB *db ) : TQVBox( parent ) { // Initialize internal variables database = db; mealNumber = 1; dayNumber = 1; dietRList = new RecipeList(); //Design the dialog setSpacing( 5 ); // Options Box optionsBox = new TQHBox( this ); daysSliderBox = new TQVGroupBox( i18n( "Number of Days" ), optionsBox ); dayNumberLabel = new TQLabel( daysSliderBox ); dayNumberLabel->setText( "- 1 -" ); dayNumberLabel->setAlignment( TQt::AlignHCenter ); dayNumberSelector = new TQSlider( daysSliderBox ); dayNumberSelector->setOrientation( TQt::Horizontal ); dayNumberSelector->setRange( 1, 10 ); dayNumberSelector->setSteps( 1, 1 ); dayNumberSelector->setTickmarks( TQSlider::Below ); dayNumberSelector->setFixedWidth( 100 ); mealsSliderBox = new TQVGroupBox( i18n( "Meals per Day" ), optionsBox ); mealNumberLabel = new TQLabel( mealsSliderBox ); mealNumberLabel->setText( "- 1 -" ); mealNumberLabel->setAlignment( TQt::AlignHCenter ); mealNumberSelector = new TQSlider( mealsSliderBox ); mealNumberSelector->setOrientation( TQt::Horizontal ); mealNumberSelector->setRange( 1, 10 ); mealNumberSelector->setSteps( 1, 1 ); mealNumberSelector->setTickmarks( TQSlider::Below ); mealNumberSelector->setFixedWidth( 100 ); // Tabs mealTabs = new TQTabWidget( this ); mealTabs->setMargin( 5 ); // Button bar TDEIconLoader il; TQHBox *bottom_layout = new TQHBox( this ); //bottom_layout->layout()->addItem( new TQSpacerItem( 10,10, TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed ) ); okButton = new TQPushButton( bottom_layout ); okButton->setIconSet( il.loadIconSet( "button_ok", TDEIcon::Small ) ); okButton->setText( i18n( "Create the diet" ) ); TQPushButton *clearButton = new TQPushButton( bottom_layout ); clearButton->setIconSet( il.loadIconSet( "edit-clear", TDEIcon::Small ) ); clearButton->setText( i18n( "Clear" ) ); // Create Tabs //don't use newTab, it'll load data and we don't want it to do that at startup mealTab = new MealInput( mealTabs, database ); mealTabs->addTab( mealTab,i18n( "Meal 1" ) ); mealTabs->setCurrentPage( mealTabs->indexOf( mealTab ) ); // Signals & Slots connect( mealNumberSelector, TQ_SIGNAL( valueChanged( int ) ), this, TQ_SLOT( changeMealNumber( int ) ) ); connect( dayNumberSelector, TQ_SIGNAL( valueChanged( int ) ), this, TQ_SLOT( changeDayNumber( int ) ) ); connect( okButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( createDiet() ) ); connect( clearButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( clear() ) ); } DietWizardDialog::~DietWizardDialog() { delete dietRList; } void DietWizardDialog::clear() { mealNumberSelector->setValue( 1 ); dayNumberSelector->setValue( 1 ); MealInput* mealTab = ( MealInput* ) ( mealTabs->page( 0 ) ); // Get the meal mealTab->setDishNo( 3 ); mealTab->showDish( 0 ); for ( uint i = 0; i < mealTab->dishInputList.count(); ++i ) { DishInput* dishInput = mealTab->dishInputList[ i ]; // Get the dish input dishInput->clear(); } } void DietWizardDialog::reload( ReloadFlags flag ) { for ( int i = 0; i < mealTabs->count(); ++i ) { MealInput *mealTab = (MealInput*)mealTabs->page(i); mealTab->reload(flag); } } void DietWizardDialog::newTab( const TQString &name ) { mealTab = new MealInput( mealTabs, database ); mealTab->reload(); mealTabs->addTab( mealTab, name ); mealTabs->setCurrentPage( mealTabs->indexOf( mealTab ) ); } void DietWizardDialog::changeMealNumber( int mn ) { mealNumberLabel->setText( TQString( i18n( "- %1 -" ) ).arg( mn ) ); if ( mn > mealNumber ) { while ( mealNumber != mn ) { mealNumber++; newTab( i18n( "Meal %1" ).arg( mealNumber ) ); } } else if ( mn < mealNumber ) { while ( mealNumber != mn ) { mealNumber--; delete mealTabs->page( mealTabs->count() - 1 ); } } } void DietWizardDialog::changeDayNumber( int dn ) { if ( dn < 7 ) { dayNumber = dn; dayNumberLabel->setText( TQString( i18n( "- %1 -" ) ).arg( dn ) ); } else if ( dn == 7 ) { dayNumber = 7; dayNumberLabel->setText( TQString( i18n( "- 1 week -" ) ) ); } else { dayNumber = ( dn - 6 ) * 7; dayNumberLabel->setText( TQString( i18n( "- %1 weeks -" ) ).arg( dn - 6 ) ); } } void DietWizardDialog::createDiet( void ) { TDEApplication::setOverrideCursor( KCursor::waitCursor() ); START_TIMER("Creating the diet"); RecipeList rlist; dietRList->clear(); // Get the whole list of recipes, detailed int flags = RecipeDB::Title | getNecessaryFlags(); database->loadRecipes( &rlist, flags ); // temporal iterator list so elements can be removed without reloading them again from the DB // this list prevents the same meal from showing up in the same day twice TQValueList tempRList; bool alert = false; for ( int day = 0;day < dayNumber;day++ ) // Create the diet for the number of days defined by the user { populateIteratorList( rlist, &tempRList ); // temporal iterator list so elements can be removed without reloading them again from the DB for ( int meal = 0;meal < mealNumber;meal++ ) { int dishNo = ( ( MealInput* ) ( mealTabs->page( meal ) ) ) ->dishNo(); for ( int dish = 0;dish < dishNo;dish++ ) { bool found = false; TQValueList tempDishRList = tempRList; while ( ( !found ) && !tempDishRList.empty() ) { int random_index = ( int ) ( ( float ) ( kapp->random() ) / ( float ) RAND_MAX * tempDishRList.count() ); TQValueList::Iterator iit = tempDishRList.at( random_index ); // note that at() retrieves an iterator to the iterator list, so we need to use * in order to get the RecipeList::Iterator RecipeList::Iterator rit = *iit; if ( found = ( ( ( !categoryFiltering( meal, dish ) ) || checkCategories( *rit, meal, dish ) ) && checkConstraints( *rit, meal, dish ) ) ) // Check that the recipe is inside the constraint limits and in the categories specified { dietRList->append( *rit ); // Add recipe to the diet list tempRList.remove( tempRList.find(*iit) ); //can't just remove()... the iterator isn't from this list (its an iterator from tempDishRList) } else { tempDishRList.remove( iit ); // Remove this analized recipe from teh list } } if ( !found ) alert = true; } } } if ( alert ) { TDEApplication::restoreOverrideCursor(); KMessageBox::sorry( this, i18n( "I could not create a full diet list given the constraints. Either the recipe list is too short or the constraints are too demanding. " ) ); } else // show the resulting diet { // make a list of dishnumbers TQValueList dishNumbers; for ( int meal = 0;meal < mealNumber;meal++ ) { int dishNo = ( ( MealInput* ) ( mealTabs->page( meal ) ) ) ->dishNo(); dishNumbers << dishNo; } TDEApplication::restoreOverrideCursor(); // display the list DietViewDialog dietDisplay( this, *dietRList, dayNumber, mealNumber, dishNumbers ); connect( &dietDisplay, TQ_SIGNAL( signalOk() ), this, TQ_SLOT( createShoppingList() ) ); dietDisplay.exec(); } END_TIMER(); } void DietWizardDialog::populateIteratorList( RecipeList &rl, TQValueList *il ) { il->clear(); RecipeList::Iterator it; for ( it = rl.begin();it != rl.end(); ++it ) il->append( it ); } int DietWizardDialog::getNecessaryFlags() const { bool need_ingredients = false; bool need_categories = false; for ( int meal = 0;meal < mealNumber;meal++ ) { int dishNo = ( ( MealInput* ) ( mealTabs->page( meal ) ) ) ->dishNo(); for ( int dish = 0;dish < dishNo;dish++ ) { if ( !need_categories ) { if ( categoryFiltering( meal, dish ) ) { need_categories = true; } } if ( !need_ingredients ) { ConstraintList constraints; loadConstraints( meal, dish, &constraints ); for ( ConstraintList::const_iterator ct_it = constraints.begin(); ct_it != constraints.end(); ++ct_it ) { if ( (*ct_it).enabled ) { need_ingredients = true; break; } } } if ( need_ingredients && need_categories ) break; } if ( need_ingredients && need_categories ) break; } kdDebug()<<"Do we need to load ingredients: "<setSpacing( 10 ); // Options box mealOptions = new TQHBox( this ); mealOptions->setSpacing( 10 ); layout->addWidget( mealOptions ); // Number of dishes input dishNumberBox = new TQHBox( mealOptions ); dishNumberBox->setSpacing( 10 ); dishNumberLabel = new TQLabel( i18n( "No. of dishes: " ), dishNumberBox ); dishNumberInput = new TQSpinBox( dishNumberBox ); dishNumberInput->setMinValue( 1 ); dishNumberInput->setMaxValue( 10 ); dishNumberBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum ) ); // Toolbar toolBar = new TQHGroupBox( mealOptions ); toolBar->setFrameStyle ( TQFrame::Panel | TQFrame::Sunken ); toolBar->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::Minimum ) ); // Next dish/ Previous dish buttons TDEIconLoader il; buttonPrev = new TQToolButton( toolBar ); buttonPrev->setUsesTextLabel( true ); buttonPrev->setTextLabel( i18n( "Previous Dish" ) ); buttonPrev->setIconSet( il.loadIconSet( "back", TDEIcon::Small ) ); buttonPrev->setTextPosition( TQToolButton::Under ); buttonNext = new TQToolButton( toolBar ); buttonNext->setUsesTextLabel( true ); buttonNext->setTextLabel( i18n( "Next Dish" ) ); buttonNext->setIconSet( il.loadIconSet( "forward", TDEIcon::Small ) ); buttonNext->setTextPosition( TQToolButton::Under ); // Dish widgets dishStack = new TQWidgetStack( this ); layout->addWidget( dishStack ); dishStack->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); // Add default dishes DishInput *newDish = new DishInput( this, database, i18n( "1st Course" ) ); dishStack->addWidget( newDish ); dishInputList.append( newDish ); newDish = new DishInput( this, database, i18n( "2nd Course" ) ); dishStack->addWidget( newDish ); dishInputList.append( newDish ); newDish = new DishInput( this, database, i18n( "Dessert" ) ); dishStack->addWidget( newDish ); dishInputList.append( newDish ); dishNumber = 3; dishNumberInput->setValue( dishNumber ); // Signals & Slots connect( dishNumberInput, TQ_SIGNAL( valueChanged( int ) ), this, TQ_SLOT( changeDishNumber( int ) ) ); connect( buttonPrev, TQ_SIGNAL( clicked() ), this, TQ_SLOT( prevDish() ) ); connect( buttonNext, TQ_SIGNAL( clicked() ), this, TQ_SLOT( nextDish() ) ); } MealInput::~MealInput() {} void MealInput::reload( ReloadFlags flag ) { TQValueList::iterator it; for ( it = dishInputList.begin(); it != dishInputList.end(); ++it ) { DishInput *di = ( *it ); di->reload(flag); } } void MealInput::setDishNo( int dn ) { dishNumberInput->setValue( dn ); } void MealInput::changeDishNumber( int dn ) { if ( dn > dishNumber ) { while ( dishNumber != dn ) { DishInput * newDish = new DishInput( this, database, TQString( i18n( "Dish %1" ) ).arg( dishNumber + 1 ) ); newDish->reload(); dishStack->addWidget( newDish ); dishInputList.append( newDish ); dishStack->raiseWidget( newDish ); dishNumber++; } } else if ( dn < dishNumber ) { TQValueList ::Iterator it; while ( dishNumber != dn ) { it = dishInputList.fromLast(); DishInput *lastDish = ( *it ); dishInputList.remove( it ); if ( ( *it ) == ( DishInput* ) dishStack->visibleWidget() ) { it--; dishStack->raiseWidget( *it ); } delete lastDish; dishNumber--; } } } void MealInput::nextDish( void ) { // First get the place of the current dish input in the list TQValueList ::iterator it = dishInputList.find( ( DishInput* ) ( dishStack->visibleWidget() ) ); //Show the next dish if it exists it++; if ( it != dishInputList.end() ) { dishStack->raiseWidget( *it ); } } void MealInput::prevDish( void ) { // First get the place of the current dish input in the list TQValueList ::iterator it = dishInputList.find( ( DishInput* ) ( dishStack->visibleWidget() ) ); //Show the previous dish if it exists it--; if ( it != dishInputList.end() ) { dishStack->raiseWidget( *it ); } } void MealInput::showDish( int dn ) { TQValueList ::iterator it = dishInputList.at( dn ); if ( it != dishInputList.end() ) dishStack->raiseWidget( *it ); } DishInput::DishInput( TQWidget* parent, RecipeDB *db, const TQString &title ) : TQWidget( parent ), database(db) { // Initialize internal variables categoryFiltering = false; // Design the widget TQVBoxLayout *layout = new TQVBoxLayout( this ); layout->setSpacing( 10 ); //Horizontal Box to hold the TDEListView's listBox = new TQHGroupBox( i18n( "Dish Characteristics" ), this ); layout->addWidget( listBox ); // Dish id dishTitle = new DishTitle( listBox, title ); //Categories list categoriesBox = new TQVBox( listBox ); categoriesEnabledBox = new TQCheckBox( categoriesBox ); categoriesEnabledBox->setText( i18n( "Enable Category Filtering" ) ); categoriesView = new CategoryCheckListView( categoriesBox, database, false ); categoriesView->setSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Ignored ); categoriesView->setEnabled( false ); // Disable it by default //Constraints list constraintsView = new PropertyConstraintListView( listBox, database ); constraintsView->setSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Ignored ); constraintsView->reload(); // KDoubleInput based edit boxes constraintsEditBox1 = new KDoubleNumInput( constraintsView->viewport() ); constraintsView->addChild( constraintsEditBox1 ); constraintsEditBox1->hide(); constraintsEditBox2 = new KDoubleNumInput( constraintsView->viewport() ); constraintsView->addChild( constraintsEditBox2 ); constraintsEditBox2->hide(); // Connect Signals & Slots connect( constraintsView, TQ_SIGNAL( executed( TQListViewItem* ) ), this, TQ_SLOT( insertConstraintsEditBoxes( TQListViewItem* ) ) ); connect( constraintsView, TQ_SIGNAL( selectionChanged() ), this, TQ_SLOT( hideConstraintInputs() ) ); connect( constraintsEditBox1, TQ_SIGNAL( valueChanged( double ) ), this, TQ_SLOT( setMinValue( double ) ) ); connect( constraintsEditBox2, TQ_SIGNAL( valueChanged( double ) ), this, TQ_SLOT( setMaxValue( double ) ) ); connect( categoriesEnabledBox, TQ_SIGNAL( toggled( bool ) ), this, TQ_SLOT( enableCategories( bool ) ) ); } DishInput::~DishInput() {} void DishInput::clear() { TQListViewItemIterator it( categoriesView ); while ( it.current() ) { TQCheckListItem * item = ( TQCheckListItem* ) it.current(); item->setOn( false ); ++it; } constraintsView->reload(); categoriesEnabledBox->setChecked( false ); } void DishInput::enableCategories( bool enable ) { categoriesView->setEnabled( enable ); categoryFiltering = enable; } bool DishInput::isCategoryFilteringEnabled( void ) const { return categoryFiltering; } void DishInput::reload( ReloadFlags flag ) { constraintsView->reload(); categoriesView->reload(flag); } void DishInput::insertConstraintsEditBoxes( TQListViewItem* it ) { TQRect r; // Constraints Box1 r = constraintsView->header() ->sectionRect( 2 ); //start at the section 2 header r.moveBy( 0, constraintsView->itemRect( it ).y() ); //Move down to the item, note that its height is same as header's right now. r.setHeight( it->height() ); // Set the item's height r.setWidth( constraintsView->header() ->sectionRect( 2 ).width() ); // and width constraintsEditBox1->setGeometry( r ); //Constraints Box2 r = constraintsView->header() ->sectionRect( 3 ); //start at the section 3 header r.moveBy( 0, constraintsView->itemRect( it ).y() ); //Move down to the item r.setHeight( it->height() ); // Set the item's height r.setWidth( constraintsView->header() ->sectionRect( 3 ).width() ); // and width constraintsEditBox2->setGeometry( r ); // Set the values from the item constraintsEditBox1->setValue( ( ( ConstraintsListItem* ) it ) ->minVal() ); constraintsEditBox2->setValue( ( ( ConstraintsListItem* ) it ) ->maxVal() ); // Show Boxes constraintsEditBox1->show(); constraintsEditBox2->show(); } void DishInput::hideConstraintInputs() { constraintsEditBox1->hide(); constraintsEditBox2->hide(); } void DishInput::loadConstraints( ConstraintList *constraints ) const { constraints->clear(); Constraint constraint; for ( ConstraintsListItem * it = ( ConstraintsListItem* ) ( constraintsView->firstChild() );it;it = ( ConstraintsListItem* ) ( it->nextSibling() ) ) { constraint.id = it->propertyId(); constraint.min = it->minVal(); constraint.max = it->maxVal(); constraint.enabled = it->isOn(); constraints->append( constraint ); } } void DishInput::loadEnabledCategories( ElementList* categories ) { categories->clear(); // Only load those that are checked, unless filtering is disabled if ( !categoriesView->isEnabled() ) { database->loadCategories(categories); } else { *categories = categoriesView->selections(); } } void DishInput::setMinValue( double minValue ) { ConstraintsListItem *it = ( ConstraintsListItem* ) ( constraintsView->selectedItem() ); // Find selected property if ( it ) it->setMin( minValue ); } void DishInput::setMaxValue( double maxValue ) { ConstraintsListItem *it = ( ConstraintsListItem* ) ( constraintsView->selectedItem() ); // Find selected property if ( it ) it->setMax( maxValue ); } DishTitle::DishTitle( TQWidget *parent, const TQString &title ) : TQWidget( parent ) { titleText = title; } DishTitle::~DishTitle() {} void DishTitle::paintEvent( TQPaintEvent * ) { // Now draw the text TQPainter painter( this ); // First draw the decoration painter.setPen( TDEGlobalSettings::activeTitleColor() ); painter.setBrush( TQBrush( TDEGlobalSettings::activeTitleColor() ) ); painter.drawRoundRect( 0, 20, 30, height() - 40, 50, ( int ) ( 50.0 / ( height() - 40 ) * 35.0 ) ); // Now draw the text TQFont titleFont = TDEGlobalSettings::windowTitleFont (); titleFont.setPointSize( 15 ); painter.setFont( titleFont ); painter.rotate( -90 ); painter.setPen( TQColor( 0x00, 0x00, 0x00 ) ); painter.drawText( 0, 0, -height(), 30, AlignCenter, titleText ); painter.setPen( TQColor( 0xFF, 0xFF, 0xFF ) ); painter.drawText( -1, -1, -height() - 1, 29, AlignCenter, titleText ); painter.end(); } TQSize DishTitle::sizeHint () const { return ( TQSize( 40, 200 ) ); } TQSize DishTitle::minimumSizeHint() const { return ( TQSize( 40, 200 ) ); } bool DietWizardDialog::checkCategories( Recipe &rec, int meal, int dish ) { // Check if the recipe is among the categories chosen ElementList categoryList; loadEnabledCategories( meal, dish, &categoryList ); for ( ElementList::const_iterator cat_it = rec.categoryList.begin(); cat_it != rec.categoryList.end(); ++cat_it ) { if ( categoryList.containsId( ( *cat_it ).id ) ) return true; } return false; } /* ** Calculate the recipe Properties and check if they're within the constraints */ bool DietWizardDialog::checkConstraints( Recipe &rec, int meal, int dish ) { // Check if the properties are within the constraints ConstraintList constraints; loadConstraints( meal, dish, &constraints ); //load the constraints bool any_enabled = false; for ( ConstraintList::const_iterator ct_it = constraints.begin(); ct_it != constraints.end(); ++ct_it ) { if ( (*ct_it).enabled ) { any_enabled = true; break; } } if ( !any_enabled ) return true; // Calculate properties of the recipe calculateProperties( rec, database ); bool withinLimits = checkLimits( rec.properties, constraints ); return ( withinLimits ); } void DietWizardDialog::loadConstraints( int meal, int dish, ConstraintList *constraints ) const { MealInput * mealTab = ( MealInput* ) ( mealTabs->page( meal ) ); // Get the meal DishInput* dishInput = mealTab->dishInputList[ dish ]; // Get the dish input dishInput->loadConstraints( constraints ); //Load the constraints form the TDEListView } void DietWizardDialog::loadEnabledCategories( int meal, int dish, ElementList *categories ) { MealInput * mealTab = ( MealInput* ) ( mealTabs->page( meal ) ); // Get the meal DishInput* dishInput = mealTab->dishInputList[ dish ]; // Get the dish input dishInput->loadEnabledCategories( categories ); //Load the categories that have been checked in the TDEListView } bool DietWizardDialog::categoryFiltering( int meal, int dish ) const { MealInput * mealTab = ( MealInput* ) ( mealTabs->page( meal ) ); // Get the meal DishInput* dishInput = mealTab->dishInputList[ dish ]; // Get the dish input return ( dishInput->isCategoryFilteringEnabled() ); //Load the categories that have been checked in the TDEListView } bool DietWizardDialog::checkLimits( IngredientPropertyList &properties, ConstraintList &constraints ) { for ( ConstraintList::const_iterator ct_it = constraints.begin(); ct_it != constraints.end(); ++ct_it ) { if ( (*ct_it).enabled ) { IngredientPropertyList::const_iterator ip_it = properties.find( (*ct_it).id ); if ( ip_it != properties.end() ) { if ( ( (*ip_it).amount > (*ct_it).max ) || ( (*ip_it).amount < (*ct_it).min ) ) return false; } else return false; } } return true; } void DietWizardDialog::createShoppingList( void ) { emit dietReady(); } RecipeList& DietWizardDialog::dietList( void ) { return *dietRList; } #include "dietwizarddialog.moc"