diff options
Diffstat (limited to 'src/dialogs/recipeinputdialog.cpp')
-rw-r--r-- | src/dialogs/recipeinputdialog.cpp | 1641 |
1 files changed, 1641 insertions, 0 deletions
diff --git a/src/dialogs/recipeinputdialog.cpp b/src/dialogs/recipeinputdialog.cpp new file mode 100644 index 0000000..31ee38e --- /dev/null +++ b/src/dialogs/recipeinputdialog.cpp @@ -0,0 +1,1641 @@ +/*************************************************************************** +* Copyright (C) 2003-2005 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* Jason Kivlighn ([email protected]) * +* * +* Copyright (C) 2006 Jason Kivlighn ([email protected]) * +* * +* 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 "recipeinputdialog.h" + +#include <tqstring.h> +#include <tqlayout.h> +#include <tqhbox.h> +#include <tqvbox.h> +#include <tqimage.h> +#include <tqmessagebox.h> +#include <tqtooltip.h> +#include <tqdatetimeedit.h> +#include <tqdragobject.h> +#include <tqbuttongroup.h> +#include <tqradiobutton.h> +#include <tqwidgetstack.h> +#include <tqpainter.h> + +#include <tdeapplication.h> +#include <tdecompletionbox.h> +#include <tdespell.h> +#include <kurl.h> +#include <tdefiledialog.h> +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <kdebug.h> +#include <kled.h> +#include <kdialogbase.h> + +#include "selectauthorsdialog.h" +#include "resizerecipedialog.h" +#include "ingredientparserdialog.h" +#include "editratingdialog.h" +#include "createunitdialog.h" +#include "datablocks/recipe.h" +#include "datablocks/categorytree.h" +#include "datablocks/unit.h" +#include "datablocks/weight.h" +#include "backends/recipedb.h" +#include "selectcategoriesdialog.h" +#include "widgets/fractioninput.h" +#include "widgets/kretextedit.h" +#include "widgets/inglistviewitem.h" +#include "../widgets/ratingdisplaywidget.h" +#include "widgets/kwidgetlistbox.h" +#include "widgets/ingredientinputwidget.h" +#include "image.h" //Initializes default photo + +#include "profiling.h" + +enum ColorStatus { GreenStatus, RedStatus, YellowStatus }; + +ClickableLed::ClickableLed( TQWidget *parent ) : KLed(parent) +{ +} + +void ClickableLed::mouseReleaseEvent( TQMouseEvent* ) +{ + emit clicked(); +} + +ImageDropLabel::ImageDropLabel( TQWidget *parent, TQPixmap &_sourcePhoto ) : TQLabel( parent ), + sourcePhoto( _sourcePhoto ) +{ + setAcceptDrops( TRUE ); +} + +void ImageDropLabel::dragEnterEvent( TQDragEnterEvent* event ) +{ + event->accept( TQImageDrag::canDecode( event ) ); +} + +void ImageDropLabel::dropEvent( TQDropEvent* event ) +{ + TQImage image; + + if ( TQImageDrag::decode( event, image ) ) { + if ( ( image.width() > width() || image.height() > height() ) || ( image.width() < width() && image.height() < height() ) ) { + TQPixmap pm_scaled; + pm_scaled.convertFromImage( image.smoothScale( width(), height(), TQImage::ScaleMin ) ); + setPixmap( pm_scaled ); + + sourcePhoto = pm_scaled; // to save scaled later on + } + else { + setPixmap( image ); + sourcePhoto = image; + } + + emit changed(); + } +} + + +RecipeInputDialog::RecipeInputDialog( TQWidget* parent, RecipeDB *db ) : TQVBox( parent ) +{ + + // Adjust internal parameters + loadedRecipe = new Recipe(); + loadedRecipe->recipeID = -1; // No loaded recipe initially + loadedRecipe->title = TQString::null; + loadedRecipe->instructions = TQString::null; + database = db; + + TDEIconLoader *il = new TDEIconLoader; + + // Tabs + tabWidget = new TQTabWidget( this, "tabWidget" ); + tabWidget->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); + + + //------- Recipe Tab ----------------- + // Recipe Photo + + recipeTab = new TQGroupBox( tabWidget ); + recipeTab->setFrameStyle( TQFrame::NoFrame ); + recipeTab->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); + + + // Design the Dialog + TQGridLayout* recipeLayout = new TQGridLayout( recipeTab, 1, 1, 0, 0 ); + + // Border + TQSpacerItem* spacer_left = new TQSpacerItem( 10, 10, TQSizePolicy::Fixed, TQSizePolicy::Minimum ); + recipeLayout->addItem( spacer_left, 1, 0 ); + TQSpacerItem* spacer_right = new TQSpacerItem( 10, 10, TQSizePolicy::Fixed, TQSizePolicy::Minimum ); + recipeLayout->addItem( spacer_right, 1, 8 ); + TQSpacerItem* spacer_top = new TQSpacerItem( 10, 10, TQSizePolicy::Minimum , TQSizePolicy::Fixed ); + recipeLayout->addItem( spacer_top, 0, 1 ); + TQSpacerItem* spacer_bottom = new TQSpacerItem( 10, 10, TQSizePolicy::Minimum , TQSizePolicy::MinimumExpanding ); + recipeLayout->addItem( spacer_bottom, 8, 1 ); + + + TQPixmap image1( defaultPhoto ); + + photoLabel = new ImageDropLabel( recipeTab, sourcePhoto ); + photoLabel->setPixmap( image1 ); + photoLabel->setFixedSize( TQSize( 221, 166 ) ); + photoLabel->setSizePolicy( TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed ) ); + photoLabel->setAlignment( TQt::AlignHCenter | TQt::AlignVCenter ); + recipeLayout->addMultiCellWidget( photoLabel, 3, 7, 1, 1 ); + + TQVBox *photoButtonsBox = new TQVBox( recipeTab ); + + changePhotoButton = new TQPushButton( photoButtonsBox ); + changePhotoButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Ignored ) ); + changePhotoButton->setText( "..." ); + TQToolTip::add + ( changePhotoButton, i18n( "Select photo" ) ); + + TQPushButton *clearPhotoButton = new TQPushButton( photoButtonsBox ); + clearPhotoButton->setPixmap( il->loadIcon( "clear_left", TDEIcon::NoGroup, 16 ) ); + TQToolTip::add + ( clearPhotoButton, i18n( "Clear photo" ) ); + + recipeLayout->addMultiCellWidget( photoButtonsBox, 3, 7, 2, 2 ); + + + //Title->photo spacer + TQSpacerItem* title_photo = new TQSpacerItem( 10, 10, TQSizePolicy::Minimum, TQSizePolicy::Fixed ); + recipeLayout->addItem( title_photo, 2, 3 ); + + + // Title + TQVBox *titleBox = new TQVBox( recipeTab ); + titleBox->setSpacing( 5 ); + titleLabel = new TQLabel( i18n( "Recipe Name" ), titleBox ); + titleEdit = new KLineEdit( titleBox ); + titleEdit->setMinimumSize( TQSize( 360, 30 ) ); + titleEdit->setMaximumSize( TQSize( 10000, 30 ) ); + titleEdit->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed ) ); + recipeLayout->addMultiCellWidget( titleBox, 1, 1, 1, 7 ); + + + // Photo ->author spacer + TQSpacerItem* title_spacer = new TQSpacerItem( 10, 10, TQSizePolicy::Fixed, TQSizePolicy::Minimum ); + recipeLayout->addItem( title_spacer, 2, 1 ); + + // Author(s) & Categories + TQVBox *authorBox = new TQVBox( recipeTab ); // contains label and authorInput (input widgets) + authorBox->setSpacing( 5 ); + recipeLayout->addWidget( authorBox, 3, 4 ); + authorLabel = new TQLabel( i18n( "Authors" ), authorBox ); + TQHBox *authorInput = new TQHBox( authorBox ); // Contains input + button + + + authorShow = new KLineEdit( authorInput ); + authorShow->setReadOnly( true ); + authorShow->setMinimumSize( TQSize( 100, 20 ) ); + authorShow->setMaximumSize( TQSize( 10000, 20 ) ); + authorShow->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed ) ); + + + addAuthorButton = new TQPushButton( authorInput ); + addAuthorButton->setText( "+" ); + addAuthorButton->setFixedSize( TQSize( 20, 20 ) ); + addAuthorButton->setFlat( true ); + + + TQSpacerItem* author_category = new TQSpacerItem( 10, 10, TQSizePolicy::Fixed, TQSizePolicy::Minimum ); + recipeLayout->addItem( author_category, 3, 5 ); + + TQVBox *categoryBox = new TQVBox( recipeTab ); // Contains the label and categoryInput (input widgets) + categoryBox->setSpacing( 5 ); + categoryLabel = new TQLabel( i18n( "Categories" ), categoryBox ); + TQHBox *categoryInput = new TQHBox( categoryBox ); // Contains the input widgets + + categoryShow = new KLineEdit( categoryInput ); + categoryShow->setReadOnly( true ); + categoryShow->setMinimumSize( TQSize( 100, 20 ) ); + categoryShow->setMaximumSize( TQSize( 10000, 20 ) ); + categoryShow->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed ) ); + recipeLayout->addWidget( categoryBox, 4, 4 ); + + addCategoryButton = new TQPushButton( categoryInput ); + addCategoryButton->setText( "+" ); + addCategoryButton->setFixedSize( TQSize( 20, 20 ) ); + addCategoryButton->setFlat( true ); + + //Category ->Servings spacer + TQSpacerItem* category_yield = new TQSpacerItem( 10, 10, TQSizePolicy::Minimum, TQSizePolicy::Fixed ); + recipeLayout->addItem( category_yield, 5, 4 ); + + TQHBox *serv_prep_box = new TQHBox( recipeTab ); + serv_prep_box->setSpacing( 5 ); + + // Backup options + TQGroupBox *yieldGBox = new TQGroupBox( serv_prep_box, "yieldGBox" ); + yieldGBox->setTitle( i18n( "Yield" ) ); + yieldGBox->setColumns( 2 ); + + yieldLabel = new TQLabel( i18n( "Amount" ), yieldGBox ); + /*TQLabel *yieldTypeLabel = */new TQLabel( i18n( "Type" ), yieldGBox ); + yieldNumInput = new FractionInput( yieldGBox ); + yieldNumInput->setAllowRange(true); + yieldTypeEdit = new KLineEdit( yieldGBox ); + + TQVBox *prepTimeBox = new TQVBox( serv_prep_box ); + prepTimeBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Fixed ) ); + prepTimeBox->setSpacing( 5 ); + + ( void ) new TQLabel( i18n( "Preparation Time" ), prepTimeBox ); + prepTimeEdit = new TQTimeEdit( prepTimeBox ); + prepTimeEdit->setMinValue( TQTime( 0, 0 ) ); + prepTimeEdit->setDisplay( TQTimeEdit::Hours | TQTimeEdit::Minutes ); + + recipeLayout->addWidget( serv_prep_box, 6, 4 ); + + //------- END OF Recipe Tab --------------- + + //------- Ingredients Tab ----------------- + + ingredientGBox = new TQGroupBox( recipeTab ); + ingredientGBox->setFrameStyle( TQFrame::NoFrame ); + ingredientGBox->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); + TQGridLayout* ingredientsLayout = new TQGridLayout( ingredientGBox ); + + // Border + TQSpacerItem* spacerBoxLeft = new TQSpacerItem( 10, 10, TQSizePolicy::Fixed, TQSizePolicy::Minimum ); + ingredientsLayout->addItem( spacerBoxLeft, 1, 0 ); + TQSpacerItem* spacerBoxTop = new TQSpacerItem( 10, 20, TQSizePolicy::Minimum, TQSizePolicy::Fixed ); + ingredientsLayout->addItem( spacerBoxTop, 0, 1 ); + + //Input Widgets + ingInput = new IngredientInputWidget( database, ingredientGBox ); + ingredientsLayout->addMultiCellWidget( ingInput, 1, 1, 1, 5 ); + + // Spacers to list and buttons + TQSpacerItem* spacerToList = new TQSpacerItem( 10, 10, TQSizePolicy::Minimum, TQSizePolicy::Fixed ); + ingredientsLayout->addItem( spacerToList, 2, 1 ); + TQSpacerItem* spacerToButtons = new TQSpacerItem( 10, 10, TQSizePolicy::Fixed, TQSizePolicy::Minimum ); + ingredientsLayout->addItem( spacerToButtons, 3, 4 ); + + // Add, Up,down,... buttons + + addButton = new KPushButton( ingredientGBox ); + addButton->setFixedSize( TQSize( 31, 31 ) ); + addButton->setFlat( true ); + TQPixmap pm = il->loadIcon( "new", TDEIcon::NoGroup, 16 ); + addButton->setPixmap( pm ); + addButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed ) ); + ingredientsLayout->addWidget( addButton, 3, 5 ); + + // Spacer to the rest of buttons + TQSpacerItem* spacerToOtherButtons = new TQSpacerItem( 10, 10, TQSizePolicy::Minimum, TQSizePolicy::Fixed ); + ingredientsLayout->addItem( spacerToOtherButtons, 4, 5 ); + + upButton = new KPushButton( ingredientGBox ); + upButton->setFixedSize( TQSize( 31, 31 ) ); + upButton->setFlat( true ); + pm = il->loadIcon( "go-up", TDEIcon::NoGroup, 16 ); + upButton->setPixmap( pm ); + upButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed ) ); + ingredientsLayout->addWidget( upButton, 5, 5 ); + + downButton = new KPushButton( ingredientGBox ); + downButton->setFixedSize( TQSize( 31, 31 ) ); + downButton->setFlat( true ); + pm = il->loadIcon( "go-down", TDEIcon::NoGroup, 16 ); + downButton->setPixmap( pm ); + downButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed ) ); + ingredientsLayout->addWidget( downButton, 6, 5 ); + + removeButton = new KPushButton( ingredientGBox ); + removeButton->setFixedSize( TQSize( 31, 31 ) ); + removeButton->setFlat( true ); + pm = il->loadIcon( "remove", TDEIcon::NoGroup, 16 ); + removeButton->setPixmap( pm ); + removeButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed ) ); + ingredientsLayout->addWidget( removeButton, 7, 5 ); + + ingParserButton = new KPushButton( ingredientGBox ); + ingParserButton->setFixedSize( TQSize( 31, 31 ) ); + ingParserButton->setFlat( true ); + pm = il->loadIcon( "edit-paste", TDEIcon::NoGroup, 16 ); + ingParserButton->setPixmap( pm ); + ingParserButton->setSizePolicy( TQSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Fixed ) ); + ingredientsLayout->addWidget( ingParserButton, 8, 5 ); + + TQToolTip::add + ( addButton, i18n( "Add ingredient" ) ); + TQToolTip::add + ( upButton, i18n( "Move ingredient up" ) ); + TQToolTip::add + ( downButton, i18n( "Move ingredient down" ) ); + TQToolTip::add + ( removeButton, i18n( "Remove ingredient" ) ); + TQToolTip::add + ( ingParserButton, i18n( "Paste Ingredients" ) ); + + // Ingredient List + ingredientList = new TDEListView( ingredientGBox, "ingredientList" ); + ingredientList->addColumn( i18n( "Ingredient" ) ); + ingredientList->addColumn( i18n( "Amount" ) ); + ingredientList->setColumnAlignment( 1, TQt::AlignHCenter ); + ingredientList->addColumn( i18n( "Units" ) ); + ingredientList->addColumn( i18n( "Preparation Method" ) ); + ingredientList->setSorting( -1 ); // Do not sort + ingredientList->setSizePolicy( TQSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::MinimumExpanding ) ); + ingredientList->setItemsRenameable( true ); + ingredientList->setRenameable( 0, false ); //name + ingredientList->setRenameable( 1, true ); //amount + ingredientList->setRenameable( 2, true ); //units + ingredientList->setRenameable( 3, true ); //prep method + ingredientList->setDefaultRenameAction( TQListView::Reject ); + ingredientsLayout->addMultiCellWidget( ingredientList, 3, 9, 1, 3 ); + + TQHBoxLayout *propertyStatusLayout = new TQHBoxLayout( NULL, 0, 5 ); + TQLabel *propertyLabel = new TQLabel( i18n("Property Status:"), ingredientGBox ); + propertyStatusLabel = new TQLabel( ingredientGBox ); + propertyStatusLed = new ClickableLed( ingredientGBox ); + propertyStatusLed->setFixedSize( TQSize(16,16) ); + propertyStatusButton = new TQPushButton( i18n("Details..."), ingredientGBox ); + //TQPushButton *propertyUpdateButton = new TQPushButton( i18n("Update"), ingredientGBox ); + propertyStatusLayout->addWidget( propertyLabel ); + propertyStatusLayout->addWidget( propertyStatusLabel ); + propertyStatusLayout->addWidget( propertyStatusLed ); + propertyStatusLayout->addWidget( propertyStatusButton ); + //propertyStatusLayout->addWidget( propertyUpdateButton ); + TQSpacerItem* propertySpacerRight = new TQSpacerItem( 10, 10, TQSizePolicy::MinimumExpanding, TQSizePolicy::Minimum ); + propertyStatusLayout->addItem( propertySpacerRight ); + + KGuiItem updateGuiItem; + updateGuiItem.setText( i18n("Update") ); + updateGuiItem.setIconSet( il->loadIconSet( "reload", TDEIcon::NoGroup ) ); + propertyStatusDialog = new KDialogBase( KDialogBase::Swallow, i18n("Property details"), + KDialogBase::Close | KDialogBase::User1 | KDialogBase::Help, + KDialogBase::Close, this, "propertyStatusDialog", false, false, + updateGuiItem + ); + propertyStatusDialog->setHelp("property-status"); + statusTextView = new TQTextEdit(0); + statusTextView->setTextFormat( TQt::RichText ); + statusTextView->setReadOnly(true); + propertyStatusDialog->setMainWidget( statusTextView ); + propertyStatusDialog->resize( 400, 300 ); + + ingredientsLayout->addMultiCellLayout( propertyStatusLayout, 10, 10, 1, 4 ); + + // ------- Recipe Instructions Tab ----------- + + instructionsTab = new TQGroupBox( recipeTab ); + instructionsTab->setFrameStyle( TQFrame::NoFrame ); + instructionsTab->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); + + TQVBoxLayout *instructionsLayout = new TQVBoxLayout( instructionsTab ); + + instructionsEdit = new KreTextEdit( instructionsTab ); + instructionsEdit->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); + instructionsEdit->setTabChangesFocus ( true ); + instructionsLayout->addWidget( instructionsEdit ); + + spellCheckButton = new TQToolButton( instructionsTab ); + spellCheckButton->setIconSet( il->loadIconSet( "tools-check-spelling", TDEIcon::Small ) ); + TQToolTip::add + ( spellCheckButton, i18n( "Check spelling" ) ); + instructionsLayout->addWidget( spellCheckButton ); + + // ------- END OF Recipe Instructions Tab ----------- + + + // ------- Recipe Ratings Tab ----------- + + TQVBox *ratingsTab = new TQVBox(recipeTab); + ratingListDisplayWidget = new KWidgetListbox(ratingsTab); + TQPushButton *addRatingButton = new TQPushButton(i18n("Add Rating..."),ratingsTab); + + connect( addRatingButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotAddRating()) ); + + // ------- END OF Recipe Ratings Tab ----------- + + + tabWidget->insertTab( recipeTab, i18n( "Recipe" ) ); + tabWidget->insertTab( ingredientGBox, i18n( "Ingredients" ) ); + tabWidget->insertTab( instructionsTab, i18n( "Instructions" ) ); + tabWidget->insertTab( ratingsTab, i18n( "Ratings" ) ); + + + // Functions Box + TQHBox* functionsLayout = new TQHBox( this ); + + functionsBox = new TQGroupBox( 1, TQt::Vertical, functionsLayout ); + functionsBox->setFrameStyle( TQFrame::NoFrame ); + + saveButton = new TQToolButton( functionsBox ); + saveButton->setIconSet( il->loadIconSet( "document-save", TDEIcon::Small ) ); + saveButton->setEnabled( false ); + showButton = new TQToolButton( functionsBox ); + showButton->setIconSet( il->loadIconSet( "viewmag", TDEIcon::Small ) ); + closeButton = new TQToolButton( functionsBox ); + closeButton->setIconSet( il->loadIconSet( "window-close", TDEIcon::Small ) ); + resizeButton = new TQToolButton( functionsBox ); + resizeButton->setIconSet( il->loadIconSet( "2uparrow", TDEIcon::Small ) ); //TODO: give me an icon :) + + saveButton->setTextLabel( i18n( "Save recipe" ), true ); + saveButton->setUsesTextLabel( true ); + showButton->setTextLabel( i18n( "Show recipe" ), true ); + showButton->setUsesTextLabel( true ); + closeButton->setTextLabel( i18n( "Close" ), true ); + closeButton->setUsesTextLabel( true ); + resizeButton->setTextLabel( i18n( "Resize recipe" ), true ); + resizeButton->setUsesTextLabel( true ); + + functionsLayout->layout() ->addItem( new TQSpacerItem( 10, 10, TQSizePolicy::MinimumExpanding, TQSizePolicy::Fixed ) ); + + // Dialog design + tabWidget->resize( size().expandedTo( minimumSizeHint() ) ); + clearWState( WState_Polished ); + + // Initialize internal data + unsavedChanges = false; // Indicates if there's something not saved yet. + enableChangedSignal(); // Enables the signal "changed()" + + // Connect signals & Slots + connect( changePhotoButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( changePhoto() ) ); + connect( clearPhotoButton, TQ_SIGNAL( clicked() ), TQ_SLOT( clearPhoto() ) ); + connect( upButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( moveIngredientUp() ) ); + connect( downButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( moveIngredientDown() ) ); + connect( removeButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( removeIngredient() ) ); + connect( addButton, TQ_SIGNAL( clicked() ), ingInput, TQ_SLOT( addIngredient() ) ); + connect( ingParserButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( slotIngredientParser() ) ); + connect( photoLabel, TQ_SIGNAL( changed() ), this, TQ_SIGNAL( changed() ) ); + connect( this, TQ_SIGNAL( changed() ), this, TQ_SLOT( recipeChanged() ) ); + connect( yieldNumInput, TQ_SIGNAL( textChanged( const TQString & ) ), this, TQ_SLOT( recipeChanged() ) ); + connect( yieldTypeEdit, TQ_SIGNAL( textChanged( const TQString & ) ), this, TQ_SLOT( recipeChanged() ) ); + connect( prepTimeEdit, TQ_SIGNAL( valueChanged( const TQTime & ) ), TQ_SLOT( recipeChanged() ) ); + connect( titleEdit, TQ_SIGNAL( textChanged( const TQString& ) ), this, TQ_SLOT( recipeChanged( const TQString& ) ) ); + connect( instructionsEdit, TQ_SIGNAL( textChanged() ), this, TQ_SLOT( recipeChanged() ) ); + connect( addCategoryButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( addCategory() ) ); + connect( addAuthorButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( addAuthor() ) ); + connect( titleEdit, TQ_SIGNAL( textChanged( const TQString& ) ), this, TQ_SLOT( prepTitleChanged( const TQString& ) ) ); + connect( ingredientList, TQ_SIGNAL( itemRenamed( TQListViewItem*, const TQString &, int ) ), TQ_SLOT( syncListView( TQListViewItem*, const TQString &, int ) ) ); + + connect ( ingInput, TQ_SIGNAL( ingredientEntered(const Ingredient&) ), this, TQ_SLOT( addIngredient(const Ingredient&) ) ); + connect ( ingInput, TQ_SIGNAL( headerEntered(const Element&) ), this, TQ_SLOT( addIngredientHeader(const Element&) ) ); + + connect( propertyStatusLed, TQ_SIGNAL(clicked()), TQ_SLOT(updatePropertyStatus()) ); + connect( propertyStatusDialog, TQ_SIGNAL(user1Clicked()), TQ_SLOT(updatePropertyStatus()) ); + connect( propertyStatusButton, TQ_SIGNAL(clicked()), propertyStatusDialog, TQ_SLOT(show()) ); + + // Function buttons + connect ( saveButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( save() ) ); + connect ( closeButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( closeOptions() ) ); + connect ( showButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( showRecipe() ) ); + connect ( resizeButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( resizeRecipe() ) ); + connect ( spellCheckButton, TQ_SIGNAL( clicked() ), this, TQ_SLOT( spellCheck() ) ); + connect ( this, TQ_SIGNAL( enableSaveOption( bool ) ), this, TQ_SLOT( enableSaveButton( bool ) ) ); + + connect ( database, TQ_SIGNAL( recipeRemoved(int) ), this, TQ_SLOT( recipeRemoved(int) ) ); + + delete il; + + //FIXME: We've got some sort of build issue... we get undefined references to CreateUnitDialog without this dummy code here + CreateUnitDialog d( this, "" ); +} + + +RecipeInputDialog::~RecipeInputDialog() +{ + delete loadedRecipe; +} + +void RecipeInputDialog::recipeRemoved( int id ) +{ + if ( loadedRecipe->recipeID == id ) { + loadedRecipe->recipeID = -1; + recipeChanged(); + } +} + +void RecipeInputDialog::prepTitleChanged( const TQString &title ) +{ + //we don't want the menu to grow due to a long title + //### KStringHandler::rsqueeze does this but I can't remember when it was added (compatibility issue...) + TQString short_title = title.left( 20 ); + if ( title.length() > 20 ) + short_title.append( "..." ); + + emit titleChanged( short_title ); +} + +int RecipeInputDialog::loadedRecipeID() const +{ + return loadedRecipe->recipeID; +} + +void RecipeInputDialog::loadRecipe( int recipeID ) +{ + emit enableSaveOption( false ); + unsavedChanges = false; + + //Disable changed() signals + enableChangedSignal( false ); + + //Empty current recipe + loadedRecipe->empty(); + + //Set back to the first page + tabWidget->setCurrentPage( 0 ); + + // Load specified Recipe ID + database->loadRecipe( loadedRecipe, RecipeDB::All ^ RecipeDB::Meta ^ RecipeDB::Properties, recipeID ); + + reload(); + + propertyStatusDialog->hide(); + updatePropertyStatus(); + + //Enable changed() signals + enableChangedSignal(); + +} + +void RecipeInputDialog::reload( void ) +{ + yieldNumInput->setValue( 1, 0 ); + yieldTypeEdit->setText(""); + ingredientList->clear(); + ratingListDisplayWidget->clear(); + ingInput->clear(); + + //Load Values in Interface + titleEdit->setText( loadedRecipe->title ); + instructionsEdit->setText( loadedRecipe->instructions ); + yieldNumInput->setValue( loadedRecipe->yield.amount, loadedRecipe->yield.amount_offset ); + yieldTypeEdit->setText( loadedRecipe->yield.type ); + prepTimeEdit->setTime( loadedRecipe->prepTime ); + + //show ingredient list + IngredientList list_copy = loadedRecipe->ingList; + for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) { + TQListViewItem * lastElement = ingredientList->lastItem(); + TQListViewItem *ing_header = 0; + + TQString group = group_list[ 0 ].group; + if ( !group.isEmpty() ) { + if ( lastElement && lastElement->parent() ) + lastElement = lastElement->parent(); + + ing_header = new IngGrpListViewItem( ingredientList, lastElement, group_list[ 0 ].group, group_list[ 0 ].groupID ); + ing_header->setOpen( true ); + lastElement = ing_header; + } + + for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) { + //Insert ingredient after last one + if ( ing_header ) { + lastElement = new IngListViewItem ( ing_header, lastElement, *ing_it ); + } + else { + if ( lastElement && lastElement->parent() ) + lastElement = lastElement->parent(); + lastElement = new IngListViewItem ( ingredientList, lastElement, *ing_it ); + } + + for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) { + new IngSubListViewItem ( lastElement, *sub_it ); + lastElement->setOpen(true); + } + + //update completion + instructionsEdit->addCompletionItem( ( *ing_it ).name ); + } + } + // + //show photo + if ( !loadedRecipe->photo.isNull() ) { + + // //get the photo + sourcePhoto = loadedRecipe->photo; + + if ( ( sourcePhoto.width() > photoLabel->width() || sourcePhoto.height() > photoLabel->height() ) || ( sourcePhoto.width() < photoLabel->width() && sourcePhoto.height() < photoLabel->height() ) ) { + TQImage pm = sourcePhoto.convertToImage(); + TQPixmap pm_scaled; + pm_scaled.convertFromImage( pm.smoothScale( photoLabel->width(), photoLabel->height(), TQImage::ScaleMin ) ); + photoLabel->setPixmap( pm_scaled ); + + sourcePhoto = pm_scaled; // to save scaled later on + } + else { + photoLabel->setPixmap( sourcePhoto ); + } + } + else { + TQPixmap photo = TQPixmap( defaultPhoto ); + photoLabel->setPixmap( photo ); + sourcePhoto = TQPixmap(); + } + + + // Show categories + showCategories(); + + // Show authors + showAuthors(); + + // Show ratings + for ( RatingList::iterator rating_it = loadedRecipe->ratingList.begin(); rating_it != loadedRecipe->ratingList.end(); ++rating_it ) { + RatingDisplayWidget *item = new RatingDisplayWidget; + item->rating_it = rating_it; + addRating(*rating_it,item); + ratingListDisplayWidget->insertItem(item); + } + ratingListDisplayWidget->ensureCellVisible(0,0); + + // Update yield type auto completion + TDECompletion *completion = yieldTypeEdit->completionObject(); + completion->clear(); + ElementList yieldList; + database->loadYieldTypes( &yieldList ); + for ( ElementList::const_iterator it = yieldList.begin(); it != yieldList.end(); ++it ) { + completion->addItem( (*it).name ); + } +} + +void RecipeInputDialog::changePhoto( void ) +{ + // standard filedialog + KURL filename = KFileDialog::getOpenURL( TQString::null, TQString( "*.png *.jpg *.jpeg *.xpm *.gif|%1 (*.png *.jpg *.jpeg *.xpm *.gif)" ).arg( i18n( "Images" ) ), this ); + TQPixmap pixmap ( filename.path() ); + if ( !( pixmap.isNull() ) ) { + // If photo is bigger than the label, or smaller in width, than photoLabel, scale it + sourcePhoto = pixmap; + if ( ( sourcePhoto.width() > photoLabel->width() || sourcePhoto.height() > photoLabel->height() ) || ( sourcePhoto.width() < photoLabel->width() && sourcePhoto.height() < photoLabel->height() ) ) { + TQImage pm = sourcePhoto.convertToImage(); + TQPixmap pm_scaled; + pm_scaled.convertFromImage( pm.smoothScale( photoLabel->width(), photoLabel->height(), TQImage::ScaleMin ) ); + photoLabel->setPixmap( pm_scaled ); + + sourcePhoto = pm_scaled; // to save scaled later on + photoLabel->setPixmap( pm_scaled ); + } + else { + photoLabel->setPixmap( sourcePhoto ); + } + emit changed(); + } +} + +void RecipeInputDialog::clearPhoto( void ) +{ + sourcePhoto = TQPixmap(); + photoLabel->setPixmap( TQPixmap( defaultPhoto ) ); + + emit changed(); +} + +void RecipeInputDialog::moveIngredientUp( void ) +{ + TQListViewItem * it = ingredientList->selectedItem(); + if ( !it || it->rtti() == INGSUBLISTVIEWITEM_RTTI ) + return ; + + TQListViewItem *iabove = it->itemAbove(); + while ( iabove && iabove->rtti() == INGSUBLISTVIEWITEM_RTTI ) + iabove = iabove->itemAbove(); + + if ( iabove ) { + if ( it->rtti() == INGGRPLISTVIEWITEM_RTTI ) { + if ( iabove->parent() ) + iabove = iabove->parent(); + + int it_index = ingItemIndex( ingredientList, it ); + int iabove_index = ingItemIndex( ingredientList, iabove ); + + iabove->moveItem( it ); //Move the Item + + loadedRecipe->ingList.move( iabove_index, ( iabove->rtti() == INGGRPLISTVIEWITEM_RTTI ) ? iabove->childCount() : 1, it_index + it->childCount() - 1 ); + } + else { + int it_index = ingItemIndex( ingredientList, it ); + int iabove_index = ingItemIndex( ingredientList, iabove ); + IngredientList::iterator ing = loadedRecipe->ingList.at( it_index ); + + if ( iabove->parent() != it->parent() ) { + if ( iabove->rtti() == INGGRPLISTVIEWITEM_RTTI && it->parent() ) { //move the item out of the group + it->parent() ->takeItem( it ); + ingredientList->insertItem( it ); + it->moveItem( ( iabove->itemAbove() ->parent() ) ? iabove->itemAbove() ->parent() : iabove->itemAbove() ); //Move the Item + } + else { //move the item into the group + ingredientList->takeItem( it ); + iabove->parent() ->insertItem( it ); + it->moveItem( iabove ); //Move the Item + } + + ingredientList->setCurrentItem( it ); //Keep selected + } + else { + iabove->moveItem( it ); //Move the Item + loadedRecipe->ingList.move( it_index, iabove_index ); + } + + if ( it->parent() ) + ( *ing ).groupID = ( ( IngGrpListViewItem* ) it->parent() ) ->id(); + else + ( *ing ).groupID = -1; + } + + emit changed(); + } +} + +void RecipeInputDialog::moveIngredientDown( void ) +{ + TQListViewItem * it = ingredientList->selectedItem(); + if ( !it || it->rtti() == INGSUBLISTVIEWITEM_RTTI ) + return ; + + TQListViewItem *ibelow = it->itemBelow(); + while ( ibelow && ibelow->rtti() == INGSUBLISTVIEWITEM_RTTI ) + ibelow = ibelow->itemBelow(); + + if ( ibelow ) { + if ( it->rtti() == INGGRPLISTVIEWITEM_RTTI ) { + TQListViewItem * next_sibling = it->nextSibling(); + + if ( next_sibling ) { + int it_index = ingItemIndex( ingredientList, it ); + int ibelow_index = ingItemIndex( ingredientList, next_sibling ); + + it->moveItem( next_sibling ); //Move the Item + + int skip = 0; + if ( next_sibling->childCount() > 0 ) + skip = next_sibling->childCount() - 1; + + loadedRecipe->ingList.move( it_index, it->childCount(), ibelow_index + skip ); + } + } + else { + int it_index = ingItemIndex( ingredientList, it ); + int ibelow_index = ingItemIndex( ingredientList, ibelow ); + IngredientList::iterator ing = loadedRecipe->ingList.at( it_index ); + + if ( ibelow->rtti() == INGGRPLISTVIEWITEM_RTTI || ( ibelow->parent() != it->parent() ) ) { + if ( ibelow->rtti() == INGGRPLISTVIEWITEM_RTTI && !it->parent() ) { //move the item into the group + if ( !it->parent() ) + ingredientList->takeItem( it ); + else + it->parent() ->takeItem( it ); + + ibelow->insertItem( it ); + } + else { //move the item out of the group + TQListViewItem *parent = it->parent(); //store this because we can't get it after we do it->takeItem() + parent->takeItem( it ); + ingredientList->insertItem( it ); + it->moveItem( parent ); //Move the Item + } + + ingredientList->setCurrentItem( it ); //Keep selected + } + else { + it->moveItem( ibelow ); //Move the Item + loadedRecipe->ingList.move( it_index, ibelow_index ); + } + + if ( it->parent() ) + ( *ing ).groupID = ( ( IngGrpListViewItem* ) it->parent() ) ->id(); + else + ( *ing ).groupID = -1; + } + + emit changed(); + } + else if ( it->parent() ) { + it->parent() ->takeItem( it ); + ingredientList->insertItem( it ); + it->moveItem( ( ingredientList->lastItem() ->parent() ) ? ingredientList->lastItem() ->parent() : ingredientList->lastItem() ); //Move the Item + ingredientList->setCurrentItem( it ); //Keep selected + + int it_index = ingItemIndex( ingredientList, it ); + IngredientList::iterator ing = loadedRecipe->ingList.at( it_index ); + ( *ing ).groupID = -1; + + emit changed(); + } +} + +void RecipeInputDialog::removeIngredient( void ) +{ + TQListViewItem * it = ingredientList->selectedItem(); + if ( it && (it->rtti() == INGLISTVIEWITEM_RTTI || it->rtti() == INGSUBLISTVIEWITEM_RTTI) ) { + TQListViewItem *iselect = it->itemBelow(); + while ( iselect && iselect->rtti() == INGSUBLISTVIEWITEM_RTTI ) + iselect = iselect->itemBelow(); + + if ( !iselect ) { + iselect = it->itemAbove(); + while ( iselect && iselect->rtti() == INGSUBLISTVIEWITEM_RTTI ) + iselect = iselect->itemAbove(); + } + + IngListViewItem *ing_item = (IngListViewItem*)it; //we can cast IngSubListViewItem to this too, it's a subclass + + IngredientData &ing = loadedRecipe->ingList.findSubstitute( ing_item->ingredient() ); + + //Remove it from the instruction's completion + instructionsEdit->removeCompletionItem( ing.name ); + + loadedRecipe->ingList.removeSubstitute( ing ); + + int ingID = ing_item->ingredient().ingredientID; + TQMap<int,TQString>::iterator map_it; + if ( (map_it = propertyStatusMapRed.find(ingID)) != propertyStatusMapRed.end() ) + propertyStatusMapRed.remove( map_it ); + else if ( (map_it = propertyStatusMapYellow.find(ingID)) != propertyStatusMapYellow.end() ) + propertyStatusMapYellow.remove( map_it ); + showStatusIndicator(); + + //Now remove the ingredient + it->setSelected( false ); + delete it; + if ( iselect ) + ingredientList->setSelected( iselect, true ); // be careful iselect->setSelected doesn't work this way. + + emit changed(); + } + else if ( it && it->rtti() == INGGRPLISTVIEWITEM_RTTI ) { + IngGrpListViewItem * header = ( IngGrpListViewItem* ) it; + + for ( IngListViewItem * sub_item = (IngListViewItem*)header->firstChild(); sub_item; sub_item = (IngListViewItem*)sub_item->nextSibling() ) { + IngredientData &ing = loadedRecipe->ingList.findSubstitute( sub_item->ingredient() ); + + //Remove it from the instruction's completion + instructionsEdit->removeCompletionItem( ing.name ); + + loadedRecipe->ingList.removeSubstitute( ing ); + + int ingID = sub_item->ingredient().ingredientID; + TQMap<int,TQString>::iterator map_it; + if ( (map_it = propertyStatusMapRed.find(ingID)) != propertyStatusMapRed.end() ) + propertyStatusMapRed.remove( map_it ); + else if ( (map_it = propertyStatusMapYellow.find(ingID)) != propertyStatusMapYellow.end() ) + propertyStatusMapYellow.remove( map_it ); + showStatusIndicator(); + } + + delete header; + + emit changed(); + } + +} + +int RecipeInputDialog::createNewYieldIfNecessary( const TQString &yield ) +{ + if ( yield.stripWhiteSpace().isEmpty() ) //no yield + return -1; + else + { + int id = database->findExistingYieldTypeByName( yield ); + if ( id == -1 ) //creating new + { + database->createNewYieldType( yield ); + id = database->lastInsertID(); + } + + return id; + } +} + +void RecipeInputDialog::syncListView( TQListViewItem* it, const TQString &new_text, int col ) +{ + if ( it->rtti() != INGLISTVIEWITEM_RTTI ) + return ; + + IngListViewItem *ing_item = ( IngListViewItem* ) it; + + IngredientData &new_ing = loadedRecipe->ingList.findSubstitute( ing_item->ingredient() ); + + switch ( col ) { + case 1: //amount + { + bool ok; + + Ingredient new_ing_amount; + new_ing_amount.setAmount(new_text,&ok); + + if ( ok ) + { + if ( new_ing.amount != new_ing_amount.amount || + new_ing.amount_offset != new_ing_amount.amount_offset ) { + new_ing.amount = new_ing_amount.amount; + new_ing.amount_offset = new_ing_amount.amount_offset; + if ( !new_text.isEmpty() ) + ing_item->setAmount( new_ing_amount.amount, new_ing_amount.amount_offset ); + + new_ing.amount = new_ing_amount.amount; + new_ing.amount_offset = new_ing_amount.amount_offset; + emit changed(); + } + } + else + { + if ( !new_text.isEmpty() ) + ing_item->setAmount( new_ing.amount, new_ing.amount_offset ); + } + + break; + } + case 2: //unit + { + Unit old_unit = new_ing.units; + + if ( new_text.length() > uint(database->maxUnitNameLength()) ) + { + KMessageBox::error( this, TQString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) ); + ing_item->setUnit( old_unit ); + break; + } + + TQString approp_unit = new_ing.amount > 1 ? new_ing.units.plural : new_ing.units.name; + if ( approp_unit != new_text.stripWhiteSpace() ) + { + Unit new_unit; + int new_id = IngredientInputWidget::createNewUnitIfNecessary( new_text.stripWhiteSpace(), new_ing.amount > 1, ing_item->ingredient().ingredientID, new_unit, database ); + + if ( new_id != -1 ) { + new_ing.units = new_unit; + new_ing.units.id = new_id; + + ing_item->setUnit( new_ing.units ); + + updatePropertyStatus(); + emit changed(); + } + else { + ing_item->setUnit( old_unit ); + } + } + break; + } + case 3: //prep method + { + TQString old_text = new_ing.prepMethodList.join(","); + + TQStringList prepMethodList = TQStringList::split(",",new_text.stripWhiteSpace()); + + 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() ) ); + ing_item->setPrepMethod( old_text ); + break; + } + } + + if ( old_text != new_text.stripWhiteSpace() ) + { + new_ing.prepMethodList = ElementList::split(",",new_text.stripWhiteSpace()); + TQValueList<int> new_ids = IngredientInputWidget::createNewPrepIfNecessary( new_ing.prepMethodList, database ); + + TQValueList<int>::const_iterator id_it = new_ids.begin(); + for ( ElementList::iterator it = new_ing.prepMethodList.begin(); it != new_ing.prepMethodList.end(); ++it, ++id_it ) { + (*it).id = *id_it; + } + + updatePropertyStatus(); + emit changed(); + } + break; + } + } +} + +void RecipeInputDialog::recipeChanged( void ) +{ + if ( changedSignalEnabled ) { + // Enable Save Button + emit enableSaveOption( true ); + emit createButton( this, titleEdit->text() ); + unsavedChanges = true; + + } + +} + +void RecipeInputDialog::recipeChanged( const TQString & /*t*/ ) +{ + recipeChanged(); // jumps to the real slot function +} + +void RecipeInputDialog::enableChangedSignal( bool en ) +{ + changedSignalEnabled = en; +} + +bool RecipeInputDialog::save ( void ) +{ + //check bounds first + if ( titleEdit->text().length() > uint(database->maxRecipeTitleLength()) ) { + KMessageBox::error( this, TQString( i18n( "Recipe title cannot be longer than %1 characters." ) ).arg( database->maxRecipeTitleLength() ), i18n( "Unable to save recipe" ) ); + return false; + } + + emit enableSaveOption( false ); + saveRecipe(); + unsavedChanges = false; + + return true; +} + +void RecipeInputDialog::saveRecipe( void ) +{ + // Nothing except for the ingredient list (loadedRecipe->ingList) + // was stored before for performance. (recipeID is already there) + + loadedRecipe->photo = sourcePhoto; + loadedRecipe->instructions = instructionsEdit->text(); + loadedRecipe->title = titleEdit->text(); + yieldNumInput->value(loadedRecipe->yield.amount,loadedRecipe->yield.amount_offset); + loadedRecipe->yield.type_id = createNewYieldIfNecessary(yieldTypeEdit->text()); + loadedRecipe->prepTime = prepTimeEdit->time(); + + // Now save() + kdDebug() << "Saving..." << endl; + database->saveRecipe( loadedRecipe ); + + +} + +void RecipeInputDialog::newRecipe( void ) +{ + loadedRecipe->empty(); + TQPixmap image( defaultPhoto ); + photoLabel->setPixmap( image ); + sourcePhoto = TQPixmap(); + + instructionsEdit->setText( i18n( "Write the recipe instructions here" ) ); + instructionsEdit->clearCompletionItems(); + titleEdit->setText( i18n( "Write the recipe title here" ) ); + ingredientList->clear(); + authorShow->clear(); + categoryShow->clear(); + yieldNumInput->setValue( 1, 0 ); + yieldTypeEdit->setText(""); + prepTimeEdit->setTime( TQTime( 0, 0 ) ); + + instructionsEdit->selectAll(); + + //Set back to the first page + tabWidget->setCurrentPage( 0 ); + + ingInput->clear(); + + //Set focus to the title + titleEdit->setFocus(); + titleEdit->selectAll(); + + //clear status info + propertyStatusMapRed.clear(); + propertyStatusMapYellow.clear(); + showStatusIndicator(); +} + +bool RecipeInputDialog::everythingSaved() +{ + return ( !( unsavedChanges ) ); +} + +void RecipeInputDialog::addCategory( void ) +{ + SelectCategoriesDialog *editCategoriesDialog = new SelectCategoriesDialog( this, loadedRecipe->categoryList, database ); + + if ( editCategoriesDialog->exec() == TQDialog::Accepted ) { // user presses Ok + loadedRecipe->categoryList.clear(); + editCategoriesDialog->getSelectedCategories( &( loadedRecipe->categoryList ) ); // get the category list chosen + emit( recipeChanged() ); //Indicate that the recipe changed + + } + + delete editCategoriesDialog; + + // show category list + showCategories(); + + +} + +void RecipeInputDialog::showCategories( void ) +{ + TQString categories; + for ( ElementList::const_iterator cat_it = loadedRecipe->categoryList.begin(); cat_it != loadedRecipe->categoryList.end(); ++cat_it ) { + if ( !categories.isEmpty() ) + categories += ","; + categories += ( *cat_it ).name; + } + categoryShow->setText( categories ); +} + +void RecipeInputDialog::addAuthor( void ) +{ + SelectAuthorsDialog * editAuthorsDialog = new SelectAuthorsDialog( this, loadedRecipe->authorList, database ); + + + if ( editAuthorsDialog->exec() == TQDialog::Accepted ) { // user presses Ok + loadedRecipe->authorList.clear(); + editAuthorsDialog->getSelectedAuthors( &( loadedRecipe->authorList ) ); // get the category list chosen + emit( recipeChanged() ); //Indicate that the recipe changed + } + + delete editAuthorsDialog; + + // show authors list + showAuthors(); +} + +void RecipeInputDialog::showAuthors( void ) +{ + TQString authors; + for ( ElementList::const_iterator author_it = loadedRecipe->authorList.begin(); author_it != loadedRecipe->authorList.end(); ++author_it ) { + if ( !authors.isEmpty() ) + authors += ","; + authors += ( *author_it ).name; + } + authorShow->setText( authors ); +} + +void RecipeInputDialog::enableSaveButton( bool enabled ) +{ + saveButton->setEnabled( enabled ); +} + +void RecipeInputDialog::closeOptions( void ) +{ + + // First check if there's anything unsaved in the recipe + if ( unsavedChanges ) { + + switch ( KMessageBox::questionYesNoCancel( this, i18n( "This recipe contains unsaved changes.\n" "Would you like to save it before closing?" ), i18n( "Unsaved changes" ) ) ) { + case KMessageBox::Yes: + save(); + break; + case KMessageBox::No: + break; + case KMessageBox::Cancel: + return ; + } + + } + + emit enableSaveOption( false ); + unsavedChanges = false; + + // Now close really + emit closeRecipe(); + + +} + +void RecipeInputDialog::showRecipe( void ) +{ + // First check if there's anything unsaved in the recipe + + if ( loadedRecipe->recipeID == -1 ) { + switch ( KMessageBox::questionYesNo( this, i18n( "You need to save the recipe before displaying it. Would you like to save it now?" ), i18n( "Unsaved changes" ) ) ) { + case KMessageBox::Yes: + save(); + break; + case KMessageBox::No: + return ; + } + } + else if ( unsavedChanges ) { + + switch ( KMessageBox::questionYesNoCancel( this, i18n( "This recipe has changes that will not be displayed unless the recipe is saved. Would you like to save it now?" ), i18n( "Unsaved changes" ) ) ) { + case KMessageBox::Yes: + save(); + break; + case KMessageBox::No: + break; + case KMessageBox::Cancel: + return ; + } + + } + + // Now open it really + emit showRecipe( loadedRecipe->recipeID ); +} + +void RecipeInputDialog::spellCheck( void ) +{ + TQString text = instructionsEdit->text(); + KSpellConfig default_cfg( this ); + KSpell::modalCheck( text, &default_cfg ); + KMessageBox::information( this, i18n( "Spell check complete." ) ); + + if ( text != instructionsEdit->text() ) //check if there were changes + instructionsEdit->setText( text ); +} + +void RecipeInputDialog::resizeRecipe( void ) +{ + yieldNumInput->value( loadedRecipe->yield.amount, loadedRecipe->yield.amount_offset ); + ResizeRecipeDialog dlg( this, loadedRecipe ); + + if ( dlg.exec() == TQDialog::Accepted ) + reload(); +} + +int RecipeInputDialog::ingItemIndex( TQListView *listview, const TQListViewItem *item ) const +{ + if ( !item ) + return -1; + + if ( item == listview->firstChild() ) + return 0; + else { + TQListViewItemIterator it( listview->firstChild() ); + uint j = 0; + for ( ; it.current() && it.current() != item; ++it ) { + if ( it.current() ->rtti() == INGLISTVIEWITEM_RTTI ) { + if ( !it.current()->parent() || it.current()->parent()->rtti() == INGGRPLISTVIEWITEM_RTTI ) + j++; + } + } + + if ( !it.current() ) + return -1; + + return j; + } +} + +void RecipeInputDialog::slotIngredientParser() +{ + UnitList units; + database->loadUnits(&units); + IngredientParserDialog dlg(units,this); + if ( dlg.exec() == TQDialog::Accepted ) { + IngredientList ings = dlg.ingredients(); + TQStringList usedGroups; + bool haveHeader = false; + for ( IngredientList::iterator it = ings.begin(); it != ings.end(); ++it ) { + if ( !(*it).group.isEmpty() && usedGroups.find((*it).group) == usedGroups.end() ) { + int id = IngredientInputWidget::createNewGroupIfNecessary((*it).group,database); + addIngredientHeader( Element((*it).group, id) ); + haveHeader = true; + usedGroups << (*it).group; + } + (*it).ingredientID = IngredientInputWidget::createNewIngredientIfNecessary((*it).name,database); + (*it).units.id = IngredientInputWidget::createNewUnitIfNecessary((*it).units.name,false,(*it).ingredientID,(*it).units,database); + + TQValueList<int> prepIDs = IngredientInputWidget::createNewPrepIfNecessary((*it).prepMethodList,database); + TQValueList<int>::const_iterator prep_id_it = prepIDs.begin(); + for ( ElementList::iterator prep_it = (*it).prepMethodList.begin(); prep_it != (*it).prepMethodList.end(); ++prep_it, ++prep_id_it ) { + (*prep_it).id = *prep_id_it; + } + + addIngredient( *it, !haveHeader ); + + if ( usedGroups.count() > 0 && (*it).group.isEmpty() ) { + TQListViewItem *last_item = ingredientList->lastItem(); + if ( last_item->parent() ) { + last_item->parent()->takeItem( last_item ); + ingredientList->insertItem( last_item ); + last_item->moveItem( ingredientList->lastItem()->parent() ); + } + } + } + } +} + +void RecipeInputDialog::slotAddRating() +{ + ElementList criteriaList; + database->loadRatingCriterion(&criteriaList); + + EditRatingDialog ratingDlg(criteriaList,this); + if ( ratingDlg.exec() == TQDialog::Accepted ) { + Rating r = ratingDlg.rating(); + + for ( RatingCriteriaList::iterator rc_it = r.ratingCriteriaList.begin(); rc_it != r.ratingCriteriaList.end(); ++rc_it ) { + int criteria_id = database->findExistingRatingByName((*rc_it).name); + if ( criteria_id == -1 ) { + database->createNewRating((*rc_it).name); + criteria_id = database->lastInsertID(); + } + (*rc_it).id = criteria_id; + } + + RatingDisplayWidget *item = new RatingDisplayWidget; + item->rating_it = loadedRecipe->ratingList.append(r); + addRating(r,item); + ratingListDisplayWidget->insertItem(item,0); + emit( recipeChanged() ); //Indicate that the recipe changed + } +} + +void RecipeInputDialog::addRating( const Rating &rating, RatingDisplayWidget *item ) +{ + int average = tqRound(rating.average()); + if ( average >= 0 ) + item->icon->setPixmap( UserIcon(TQString("rating%1").arg(average) ) ); + else //no rating criteria, therefore no average (we don't want to automatically assume a zero average) + item->icon->clear(); + + item->raterName->setText(rating.rater); + item->comment->setText(rating.comment); + + item->criteriaListView->clear(); + for ( RatingCriteriaList::const_iterator rc_it = rating.ratingCriteriaList.begin(); rc_it != rating.ratingCriteriaList.end(); ++rc_it ) { + TQListViewItem * it = new TQListViewItem(item->criteriaListView,(*rc_it).name); + + int stars = int((*rc_it).stars * 2); //multiply by two to make it easier to work with half-stars + + TQPixmap star = UserIcon(TQString::fromLatin1("star_on")); + int pixmapWidth = 18*(stars/2)+((stars%2==1)?9:0); + TQPixmap generatedPixmap(pixmapWidth,18); + + if ( !generatedPixmap.isNull() ) { //there aren't zero stars + generatedPixmap.fill(); + TQPainter painter( &generatedPixmap ); + painter.drawTiledPixmap(0,0,pixmapWidth,18,star); + it->setPixmap(1,generatedPixmap); + } + } + + item->buttonEdit->disconnect(); + item->buttonRemove->disconnect(); + connect(item->buttonEdit, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotEditRating())); + connect(item->buttonRemove, TQ_SIGNAL(clicked()), + this, TQ_SLOT(slotRemoveRating())); +} + +void RecipeInputDialog::slotEditRating() +{ + RatingDisplayWidget *sender = (RatingDisplayWidget*)(TQObject::sender()->parent()); + + ElementList criteriaList; + database->loadRatingCriterion(&criteriaList); + + EditRatingDialog ratingDlg(criteriaList,*sender->rating_it,this); + if ( ratingDlg.exec() == TQDialog::Accepted ) { + Rating r = ratingDlg.rating(); + + for ( RatingCriteriaList::iterator rc_it = r.ratingCriteriaList.begin(); rc_it != r.ratingCriteriaList.end(); ++rc_it ) { + int criteria_id = database->findExistingRatingByName((*rc_it).name); + if ( criteria_id == -1 ) { + database->createNewRating((*rc_it).name); + criteria_id = database->lastInsertID(); + } + (*rc_it).id = criteria_id; + } + + (*sender->rating_it) = r; + addRating(r,sender); + emit recipeChanged(); //Indicate that the recipe changed + } +} + +void RecipeInputDialog::slotRemoveRating() +{ + RatingDisplayWidget *sender = (RatingDisplayWidget*)(TQObject::sender()->parent()); + loadedRecipe->ratingList.remove(sender->rating_it); + + //FIXME: sender is removed but never deleted (sender->deleteLater() doesn't work) + ratingListDisplayWidget->removeItem(sender); + + emit recipeChanged(); //Indicate that the recipe changed +} + +void RecipeInputDialog::addIngredient( const Ingredient &ing, bool noHeader ) +{ + Ingredient ingCopy = ing; + + //Append to the ListView + TQListViewItem* lastElement = ingredientList->lastItem(); + while ( lastElement && lastElement->rtti() == INGSUBLISTVIEWITEM_RTTI ) + lastElement = lastElement->itemAbove(); + + if ( noHeader && lastElement ) + lastElement = (lastElement->parent())?lastElement->parent():lastElement; + + if ( !noHeader && lastElement && + ( lastElement->rtti() == INGGRPLISTVIEWITEM_RTTI || ( lastElement->parent() && lastElement->parent() ->rtti() == INGGRPLISTVIEWITEM_RTTI ) ) ) + { + IngGrpListViewItem * header = ( lastElement->parent() ) ? ( IngGrpListViewItem* ) lastElement->parent() : ( IngGrpListViewItem* ) lastElement; + + ingCopy.groupID = header->id(); + + lastElement = new IngListViewItem( header, lastElement, ingCopy ); + for ( TQValueList<IngredientData>::const_iterator it = ingCopy.substitutes.begin(); it != ingCopy.substitutes.end(); ++it ) { + new IngSubListViewItem( lastElement, *it ); + } + lastElement->setOpen(true); + } + else { + lastElement = new IngListViewItem( ingredientList, lastElement, ingCopy ); + for ( TQValueList<IngredientData>::const_iterator it = ing.substitutes.begin(); it != ing.substitutes.end(); ++it ) { + new IngSubListViewItem( lastElement, *it ); + } + lastElement->setOpen(true); + } + + //append to recipe + loadedRecipe->ingList.append( ingCopy ); + + //update the completion in the instructions edit + instructionsEdit->addCompletionItem( ingCopy.name ); + + updatePropertyStatus( ingCopy, true ); + + emit changed(); +} + +void RecipeInputDialog::addIngredientHeader( const Element &header ) +{ + TQListViewItem *last_item = ingredientList->lastItem(); + if ( last_item && last_item->parent() ) + last_item = last_item->parent(); + + IngGrpListViewItem *ing_header = new IngGrpListViewItem( ingredientList, last_item, header.name, header.id ); + ing_header->setOpen( true ); +} + +void RecipeInputDialog::updatePropertyStatus() +{ + propertyStatusMapRed.clear(); + propertyStatusMapYellow.clear(); + + for ( IngredientList::const_iterator ing_it = loadedRecipe->ingList.begin(); ing_it != loadedRecipe->ingList.end(); ++ing_it ) { + updatePropertyStatus( *ing_it, false ); + } + + showStatusIndicator(); +} + +void RecipeInputDialog::updatePropertyStatus( const Ingredient &ing, bool updateIndicator ) +{ + IngredientPropertyList ingPropertyList; + database->loadProperties( &ingPropertyList, ing.ingredientID ); + + if ( ingPropertyList.count() == 0 ) { + propertyStatusMapRed.insert(ing.ingredientID,TQString(i18n("<b>%1:</b> No nutrient information available")).arg(ing.name)); + } + + TQMap<int,bool> ratioCache; //unit->conversion possible + IngredientPropertyList::const_iterator prop_it; + for ( prop_it = ingPropertyList.begin(); prop_it != ingPropertyList.end(); ++prop_it ) { + Ingredient result; + + TQMap<int,bool>::const_iterator cache_it = ratioCache.find((*prop_it).perUnit.id); + if ( cache_it == ratioCache.end() ) { + RecipeDB::ConversionStatus status = database->convertIngredientUnits( ing, (*prop_it).perUnit, result ); + ratioCache.insert((*prop_it).perUnit.id,status==RecipeDB::Success||status==RecipeDB::MismatchedPrepMethod); + + switch ( status ) { + case RecipeDB::Success: break; + case RecipeDB::MissingUnitConversion: { + if ( ing.units.type != Unit::Other && ing.units.type == (*prop_it).perUnit.type ) { + propertyStatusMapRed.insert(ing.ingredientID,TQString(i18n("<b>%3:</b> Unit conversion missing for conversion from '%1' to '%2'")) + .arg(ing.units.name.isEmpty()?i18n("-No unit-"):ing.units.name) + .arg((*prop_it).perUnit.name) + .arg(ing.name)); + } else { + WeightList weights = database->ingredientWeightUnits( ing.ingredientID ); + TQValueList< TQPair<int,int> > usedIds; + TQStringList missingConversions; + for ( WeightList::const_iterator weight_it = weights.begin(); weight_it != weights.end(); ++weight_it ) { + //skip entries that only differ in how it's prepared + TQPair<int,int> usedPair((*weight_it).perAmountUnitID,(*weight_it).weightUnitID); + if ( usedIds.find(usedPair) != usedIds.end() ) + continue; + + TQString toUnit = database->unitName((*weight_it).perAmountUnitID).name; + if ( toUnit.isEmpty() ) toUnit = i18n("-No unit-"); + + TQString fromUnit = database->unitName((*weight_it).weightUnitID).name; + if ( fromUnit.isEmpty() ) fromUnit = i18n("-No unit-"); + + TQString ingUnit = ing.units.name; + if ( ingUnit.isEmpty() ) ingUnit = i18n("-No unit-"); + + TQString propUnit = (*prop_it).perUnit.name; + if ( propUnit.isEmpty() ) propUnit = i18n("-No unit-"); + + missingConversions << conversionPath( ingUnit, toUnit, fromUnit, propUnit); + + usedIds << usedPair; + } + propertyStatusMapRed.insert(ing.ingredientID,TQString(i18n("<b>%1:</b> Either an appropriate ingredient weight entry is needed, or Krecipes needs conversion information to perform one of the following conversions: %2")) + .arg(ing.name) + .arg("<ul><li>"+missingConversions.join("</li><li>")+"</li></ul>") + ); + } + break; + } + case RecipeDB::MissingIngredientWeight: + propertyStatusMapRed.insert(ing.ingredientID,TQString(i18n("<b>%1:</b> No ingredient weight entries")).arg(ing.name)); + break; + case RecipeDB::MismatchedPrepMethod: + if ( ing.prepMethodList.count() == 0 ) + propertyStatusMapRed.insert(ing.ingredientID,TQString(i18n("<b>%1:</b> There is no ingredient weight entry for when no preparation method is specified")).arg(ing.name)); + else + propertyStatusMapRed.insert(ing.ingredientID,TQString(i18n("<b>%1:</b> There is no ingredient weight entry for when prepared in any of the following manners: %2")).arg(ing.name).arg("<ul><li>"+ing.prepMethodList.join("</li><li>")+"</li></ul>")); + break; + case RecipeDB::MismatchedPrepMethodUsingApprox: + propertyStatusMapYellow.insert(ing.ingredientID,TQString(i18n("<b>%1:</b> There is no ingredient weight entry for when prepared in any of the following manners (defaulting to a weight entry without a preparation method specified): %2")).arg(ing.name).arg("<ul><li>"+ing.prepMethodList.join("</li><li>")+"</li></ul>")); + break; + default: kdDebug()<<"Code error: Unhandled conversion status code "<<status<<endl; break; + } + } + } + + if ( updateIndicator ) + showStatusIndicator(); +} + +void RecipeInputDialog::showStatusIndicator() +{ + if ( propertyStatusMapRed.count() == 0 ) { + if ( propertyStatusMapYellow.count() == 0 ) { + propertyStatusLed->setColor( TQt::green ); + propertyStatusLabel->setText( i18n("Complete") ); + propertyStatusButton->setEnabled(false); + } + else { + propertyStatusLed->setColor( TQt::yellow ); + propertyStatusLabel->setText( i18n("Complete, but approximations made") ); + propertyStatusButton->setEnabled(true); + } + } + else { + propertyStatusLed->setColor( TQt::red ); + propertyStatusLabel->setText( i18n("Incomplete") ); + propertyStatusButton->setEnabled(true); + } + + if ( propertyStatusMapRed.count() == 0 && propertyStatusMapYellow.count() == 0 ) + propertyStatusDialog->hide(); + else + statusTextView->setText(statusMessage()); +} + +TQString RecipeInputDialog::statusMessage() const +{ + TQString statusMessage; + + if ( propertyStatusMapRed.count() > 0 ) { + statusMessage.append( i18n("The nutrient information for this recipe is incomplete because the following information is missing:") ); + statusMessage.append("<ul>"); + for ( TQMap<int,TQString>::const_iterator it = propertyStatusMapRed.begin(); it != propertyStatusMapRed.end(); ++it ) { + statusMessage.append("<li>"); + statusMessage.append(it.data()); + statusMessage.append("</li>"); + } + statusMessage.append("</ul>"); + } + + if ( propertyStatusMapYellow.count() > 0 ) { + statusMessage.append( i18n("The following approximations will be made when determining nutrient information:") ); + statusMessage.append("<ul>"); + for ( TQMap<int,TQString>::const_iterator it = propertyStatusMapYellow.begin(); it != propertyStatusMapYellow.end(); ++it ) { + statusMessage.append("<li>"); + statusMessage.append(it.data()); + statusMessage.append("</li>"); + } + statusMessage.append("</ul>"); + } + + return statusMessage; +} + +TQString RecipeInputDialog::conversionPath( const TQString &ingUnit, const TQString &toUnit, const TQString &fromUnit, const TQString &propUnit ) const +{ + TQString path = "'"+ingUnit+"'"; + + TQString lastUnit = ingUnit; + if ( lastUnit != toUnit ) { + path.append(" => '"+toUnit+"'"); + lastUnit = toUnit; + } + if ( lastUnit != fromUnit ) { + path.append(" => '"+fromUnit+"'"); + lastUnit = fromUnit; + } + if ( lastUnit != propUnit ) { + path.append(" => '"+propUnit+"'"); + lastUnit = propUnit; + } + return path; +} + +#include "recipeinputdialog.moc" |