/*
 * MainWindow.cpp is part of Brewtarget, and is Copyright the following
 * authors 2009-2014
 * - A.J. Drobnich <aj.drobnich@gmail.com>
 * - Dan Cavanagh <dan@dancavanagh.com>
 * - David Grundberg <individ@acc.umu.se>
 * - Kregg K <gigatropolis@yahoo.com>
 * - Maxime Lavigne <duguigne@gmail.com>
 * - Mik Firestone <mikfire@gmail.com>
 * - Philip Greggory Lee <rocketman768@gmail.com>
 * - plut0nium
 * - Samuel Östling <MrOstling@gmail.com>
 * - Ted Wright
 *
 * Brewtarget 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 3 of the License, or
 * (at your option) any later version.
 *
 * Brewtarget is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QWidget>
#include <QMainWindow>
#include <QMessageBox>
#include <QToolButton>
#include <QSize>
#include <QtGui>
#include <QString>
#include <QFileDialog>
#include <QIcon>
#include <QPixmap>
#include <QList>
#include <QVector>
#include <QVBoxLayout>
#include <QDomDocument>
#include <QFile>
#include <QIODevice>
#include <QTextStream>
#include <QDomNodeList>
#include <QDomNode>
#include <QDomElement>
#include <QInputDialog>
#include <QLineEdit>
#include <QUrl>
#include <QDesktopServices>
#include <QNetworkReply>
#include <QAction>
#include <QLinearGradient>
#include <QBrush>
#include <QPen>

#include "Algorithms.h"
#include "MashStepEditor.h"
#include "MashStepTableModel.h"
#include "mash.h"
#include "MashEditor.h"
#include "brewtarget.h"
#include "FermentableEditor.h"
#include "MiscEditor.h"
#include "HopEditor.h"
#include "YeastEditor.h"
#include "YeastTableModel.h"
#include "MiscTableModel.h"
#include "style.h"
#include "recipe.h"
#include "MainWindow.h"
#include "AboutDialog.h"
#include "database.h"
#include "YeastDialog.h"
#include "config.h"
#include "unit.h"
#include "ScaleRecipeTool.h"
#include "HopTableModel.h"
#include "BtDigitWidget.h"
#include "FermentableTableModel.h"
#include "BrewNoteWidget.h"
#include "EquipmentEditor.h"
#include "FermentableDialog.h"
#include "HopDialog.h"
#include "MashWizard.h"
#include "MiscDialog.h"
#include "StyleEditor.h"
#include "OptionDialog.h"
#include "OgAdjuster.h"
#include "ConverterTool.h"
#include "TimerListDialog.h"
#include "RecipeFormatter.h"
#include "PrimingDialog.h"
#include "StrikeWaterDialog.h"
#include "RefractoDialog.h"
#include "MashDesigner.h"
#include "PitchDialog.h"
#include "fermentable.h"
#include "yeast.h"
#include "brewnote.h"
#include "equipment.h"
#include "FermentableTableModel.h"
#include "FermentableSortFilterProxyModel.h"
#include "HopTableModel.h"
#include "HopSortFilterProxyModel.h"
#include "MiscTableModel.h"
#include "MiscSortFilterProxyModel.h"
#include "YeastSortFilterProxyModel.h"
#include "EquipmentListModel.h"
#include "StyleListModel.h"
#include "MashListModel.h"
#include "StyleSortFilterProxyModel.h"
#include "NamedMashEditor.h"
#include "BtDatePopup.h"
#if defined(Q_OS_WIN)
   #include <windows.h>
#endif

MainWindow::MainWindow(QWidget* parent)
        : QMainWindow(parent)
{
   // Need to call this to get all the widgets added (I think).
   setupUi(this);

   QDesktopWidget *desktop = QApplication::desktop();

   // Ensure database initializes.
   Database::instance();

   // Set the window title.
   setWindowTitle( QString("Brewtarget - %1").arg(VERSIONSTRING) );

   // Different palettes for some text.
   numPalette_old = label_calcBatchSize->palette();
   numPalette_tooLow = numPalette_old;
   numPalette_tooLow.setColor(QPalette::Active, QPalette::WindowText, QColor::fromRgb(0, 0, 208));
   numPalette_good = numPalette_old;
   numPalette_good.setColor(QPalette::Active, QPalette::WindowText, QColor::fromRgb(0, 128, 0));
   numPalette_tooHigh = numPalette_old;
   numPalette_tooHigh.setColor(QPalette::Active, QPalette::WindowText, QColor::fromRgb(208, 0, 0));
   
   // Null out the recipe
   recipeObs = 0;
   
   dialog_about = new AboutDialog(this);
   equipEditor = new EquipmentEditor(this);
   singleEquipEditor = new EquipmentEditor(this, true);
   fermDialog = new FermentableDialog(this);
   fermEditor = new FermentableEditor(this);
   hopDialog = new HopDialog(this);
   hopEditor = new HopEditor(this);
   mashEditor = new MashEditor(this);
   mashStepEditor = new MashStepEditor(this);
   mashWizard = new MashWizard(this);
   miscDialog = new MiscDialog(this);
   miscEditor = new MiscEditor(this);
   styleEditor = new StyleEditor(this);
   singleStyleEditor = new StyleEditor(this,true);
   yeastDialog = new YeastDialog(this);
   yeastEditor = new YeastEditor(this);
   optionDialog = new OptionDialog(this);
   recipeScaler = new ScaleRecipeTool(this);
   recipeFormatter = new RecipeFormatter(this);
   ogAdjuster = new OgAdjuster(this);
   converterTool = new ConverterTool(this);
   timerListDialog = new TimerListDialog(this);
   primingDialog = new PrimingDialog(this);
   strikeWaterDialog = new StrikeWaterDialog(this);
   refractoDialog = new RefractoDialog(this);
   mashDesigner = new MashDesigner(this);
   pitchDialog = new PitchDialog(this);
   btDatePopup = new BtDatePopup(this);

   styleRangeWidget_og->setRange(1.000, 1.120);
   styleRangeWidget_og->setPrecision(3);
   styleRangeWidget_og->setTickMarks(0.010, 2);
   
   styleRangeWidget_fg->setRange(1.000, 1.030);
   styleRangeWidget_fg->setPrecision(3);
   styleRangeWidget_fg->setTickMarks(0.010, 2);
   
   styleRangeWidget_abv->setRange(0.0, 15.0);
   styleRangeWidget_abv->setPrecision(1);
   styleRangeWidget_abv->setTickMarks(1, 2);
   
   styleRangeWidget_ibu->setRange(0.0, 120.0);
   styleRangeWidget_ibu->setPrecision(1);
   styleRangeWidget_ibu->setTickMarks(10, 2);
   
   const int srmMax = 50;
   styleRangeWidget_srm->setRange(0.0, static_cast<double>(srmMax));
   styleRangeWidget_srm->setPrecision(1);
   styleRangeWidget_srm->setTickMarks(10, 2);
   // Need to change appearance of color slider
   {
      // The styleRangeWidget_srm should display beer color in the background
      QLinearGradient grad( 0,0, 1,0 );
      grad.setCoordinateMode(QGradient::ObjectBoundingMode);
      for( int i=0; i <= srmMax; ++i )
      {
         double srm = i;
         grad.setColorAt( srm/static_cast<double>(srmMax), Algorithms::srmToColor(srm));
      }
      styleRangeWidget_srm->setBackgroundBrush(grad);

      // The styleRangeWidget_srm should display a "window" to show acceptable colors for the style
      styleRangeWidget_srm->setPreferredRangeBrush(QColor(0,0,0,0));
      styleRangeWidget_srm->setPreferredRangePen(QPen(Qt::black, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
      
      // Half-height "tick" for color marker
      grad = QLinearGradient( 0,0, 0,1 );
      grad.setCoordinateMode(QGradient::ObjectBoundingMode);
      grad.setColorAt( 0, QColor(255,255,255,255) );
      grad.setColorAt( 0.49, QColor(255,255,255,255) );
      grad.setColorAt( 0.50, QColor(255,255,255,0) );
      grad.setColorAt( 1, QColor(255,255,255,0) );
      styleRangeWidget_srm->setMarkerBrush(grad);
   }
   
   // Set equipment combo box model.
   equipmentListModel = new EquipmentListModel(equipmentComboBox);
   equipmentComboBox->setModel(equipmentListModel);

   // Set the style combo box
   styleListModel = new StyleListModel(styleComboBox);
   styleProxyModel = new StyleSortFilterProxyModel(styleComboBox);
   styleProxyModel->setDynamicSortFilter(true);
   styleProxyModel->setSourceModel(styleListModel);
   styleComboBox->setModel(styleProxyModel);
  
   // Set the mash combo box
   mashListModel =  new MashListModel(mashComboBox);
   mashComboBox->setModel(mashListModel);

   // Nothing to say.
   namedMashEditor = new NamedMashEditor(this, mashStepEditor);
   // I don't think this is used yet
   singleNamedMashEditor = new NamedMashEditor(this,mashStepEditor,true);

   // Set table models.
   // Fermentables
   fermTableModel = new FermentableTableModel(fermentableTable);
   fermTableProxy = new FermentableSortFilterProxyModel(fermentableTable,false);
   fermTableProxy->setSourceModel(fermTableModel);
   fermentableTable->setItemDelegate(new FermentableItemDelegate(fermentableTable));
   fermentableTable->setModel(fermTableProxy);
   // Make the fermentable table show grain percentages in row headers.
   fermTableModel->setDisplayPercentages(true);

   // Hops
   hopTableModel = new HopTableModel(hopTable);
   hopTableProxy = new HopSortFilterProxyModel(hopTable, false);
   hopTableProxy->setSourceModel(hopTableModel);
   hopTable->setItemDelegate(new HopItemDelegate(hopTable));
   hopTable->setModel(hopTableProxy);
   // Hop table show IBUs in row headers.
   hopTableModel->setShowIBUs(true);
   
   // Misc
   miscTableModel = new MiscTableModel(miscTable);
   miscTableProxy = new MiscSortFilterProxyModel(miscTable,false);
   miscTableProxy->setSourceModel(miscTableModel);
   miscTable->setItemDelegate(new MiscItemDelegate(miscTable));
   miscTable->setModel(miscTableProxy);
   
   // Yeast
   yeastTableModel = new YeastTableModel(yeastTable);
   yeastTableProxy = new YeastSortFilterProxyModel(yeastTable,false);
   yeastTableProxy->setSourceModel(yeastTableModel);
   yeastTable->setItemDelegate(new YeastItemDelegate(yeastTable));
   yeastTable->setModel(yeastTableProxy);
   
   // Mashes
   mashStepTableModel = new MashStepTableModel(mashStepTableWidget);
   mashStepTableWidget->setItemDelegate(new MashStepItemDelegate());
   mashStepTableWidget->setModel(mashStepTableModel);
   
   // Enable sorting in the main tables.
   fermentableTable->horizontalHeader()->setSortIndicator( FERMAMOUNTCOL, Qt::DescendingOrder );
   fermentableTable->setSortingEnabled(true);
   fermTableProxy->setDynamicSortFilter(true);
   hopTable->horizontalHeader()->setSortIndicator( HOPTIMECOL, Qt::DescendingOrder );
   hopTable->setSortingEnabled(true);
   hopTableProxy->setDynamicSortFilter(true);
   miscTable->horizontalHeader()->setSortIndicator( MISCUSECOL, Qt::DescendingOrder );
   miscTable->setSortingEnabled(true);
   miscTableProxy->setDynamicSortFilter(true);
   yeastTable->horizontalHeader()->setSortIndicator( YEASTNAMECOL, Qt::DescendingOrder );
   yeastTable->setSortingEnabled(true);
   yeastTableProxy->setDynamicSortFilter(true);
   
   // Create the keyboard shortcuts
   setupShortCuts();

   // Set up the printer
   printer = new QPrinter;
   printer->setPageSize(QPrinter::Letter);

   // Set up the fileOpener dialog.
   fileOpener = new QFileDialog(this, tr("Open"), QDir::homePath(), tr("BeerXML files (*.xml)"));
   fileOpener->setAcceptMode(QFileDialog::AcceptOpen);
   fileOpener->setFileMode(QFileDialog::ExistingFiles);
   fileOpener->setViewMode(QFileDialog::List);

   // Set up the fileSaver dialog.
   fileSaver = new QFileDialog(this, tr("Save"), QDir::homePath(), tr("BeerXML files (*.xml)") );
   fileSaver->setAcceptMode(QFileDialog::AcceptSave);
   fileSaver->setFileMode(QFileDialog::AnyFile);
   fileSaver->setViewMode(QFileDialog::List);
   fileSaver->setDefaultSuffix(QString("xml"));

   // Do some magic on the splitter widget to keep the tree from expanding
   splitter_2->setStretchFactor(0,0);
   splitter_2->setStretchFactor(1,1);

   // Once more with the context menus too
   setupContextMenu();

   // If we saved a size the last time we ran, use it
   if ( Brewtarget::hasOption("geometry"))
   {
      restoreGeometry(Brewtarget::option("geometry").toByteArray());
      restoreState(Brewtarget::option("windowState").toByteArray());
   }
   else
   {
      // otherwise, guess a reasonable size at 1/4 of the screen.
      int width = desktop->width();
      int height = desktop->height();

      this->resize(width/2,height/2);
   }

   // If we saved the selected recipe name the last time we ran, select it and show it.
   if (Brewtarget::hasOption("recipeKey"))
   {
      int key = Brewtarget::option("recipeKey").toInt();
      recipeObs = Database::instance().recipe( key );
      QModelIndex rIdx = treeView_recipe->findElement(recipeObs);

      setRecipe(recipeObs);
      setTreeSelection(rIdx);
   }     
   else
   {
      QList<Recipe*> recs = Database::instance().recipes();
      if( recs.size() > 0 )
         setRecipe( recs[0] );
   }

   // Connect signals.
   // actions
   connect( actionExit, SIGNAL( triggered() ), this, SLOT( close() ) );
   connect( actionAbout_BrewTarget, SIGNAL( triggered() ), dialog_about, SLOT( show() ) );
   connect( actionNewRecipe, SIGNAL( triggered() ), this, SLOT( newRecipe() ) );
   connect( actionImport_Recipes, SIGNAL( triggered() ), this, SLOT( importFiles() ) );
   connect( actionExportRecipe, SIGNAL( triggered() ), this, SLOT( exportRecipe() ) );
   connect( actionEquipments, SIGNAL( triggered() ), equipEditor, SLOT( show() ) );
   connect( actionMashs, SIGNAL( triggered() ), namedMashEditor, SLOT( show() ) );
   connect( actionStyles, SIGNAL( triggered() ), styleEditor, SLOT( show() ) );
   connect( actionFermentables, SIGNAL( triggered() ), fermDialog, SLOT( show() ) );
   connect( actionHops, SIGNAL( triggered() ), hopDialog, SLOT( show() ) );
   connect( actionMiscs, SIGNAL( triggered() ), miscDialog, SLOT( show() ) );
   connect( actionYeasts, SIGNAL( triggered() ), yeastDialog, SLOT( show() ) );
   connect( actionOptions, SIGNAL( triggered() ), optionDialog, SLOT( show() ) );
   connect( actionManual, SIGNAL( triggered() ), this, SLOT( openManual() ) );
   connect( actionScale_Recipe, SIGNAL( triggered() ), recipeScaler, SLOT( show() ) );
   connect( action_recipeToTextClipboard, SIGNAL( triggered() ), recipeFormatter, SLOT( toTextClipboard() ) );
   connect( actionConvert_Units, SIGNAL( triggered() ), converterTool, SLOT( show() ) );
   connect( actionOG_Correction_Help, SIGNAL( triggered() ), ogAdjuster, SLOT( show() ) );
   connect( actionBackup_Database, SIGNAL( triggered() ), this, SLOT( backup() ) );
   connect( actionRestore_Database, SIGNAL( triggered() ), this, SLOT( restoreFromBackup() ) );
   connect( actionCopy_Recipe, SIGNAL( triggered() ), this, SLOT( copyRecipe() ) );
   connect( actionPriming_Calculator, SIGNAL( triggered() ), primingDialog, SLOT( show() ) );
   connect( actionStrikeWater_Calculator, SIGNAL( triggered() ), strikeWaterDialog, SLOT( show() ) );
   connect( actionRefractometer_Tools, SIGNAL( triggered() ), refractoDialog, SLOT( show() ) );
   connect( actionPitch_Rate_Calculator, SIGNAL(triggered()), this, SLOT(showPitchDialog()));
   connect( actionMergeDatabases, SIGNAL(triggered()), this, SLOT(updateDatabase()) );
   connect( actionTimers, SIGNAL(triggered()), timerListDialog, SLOT(show()) );
   connect( actionDeleteSelected, SIGNAL(triggered()), this, SLOT(deleteSelected()) );
   connect( actionSave, SIGNAL(triggered()), this, SLOT(save()) );
   connect( actionDonate, SIGNAL( triggered() ), this, SLOT( openDonateLink() ) );

   // Printing signals/slots.
   // Refactoring is good.  It's like a rye saison fermenting away
   connect( actionRecipePrint, SIGNAL(triggered()), this, SLOT(print()));
   connect( actionRecipePreview, SIGNAL(triggered()), this, SLOT(print()));
   connect( actionRecipeHTML, SIGNAL(triggered()), this, SLOT(print()));
   connect( actionBrewdayPrint, SIGNAL(triggered()), this, SLOT(print()));
   connect( actionBrewdayPreview, SIGNAL(triggered()), this, SLOT(print()));
   connect( actionBrewdayHTML, SIGNAL(triggered()), this, SLOT(print()));

   // Connect up all the labels. I really need to find a better way.
   connect(targetBatchSizeLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(calculatedBatchSizeLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(targetBoilSizeLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(calculatedBoilSizeLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(oGLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(boilSgLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(fGLabel, SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));
   connect(colorSRMLabel,SIGNAL(labelChanged(QString)), this, SLOT(redisplayLabel(QString)));

   // Those are the easy ones. Let's see what we can do with the tables. First one wires the cells, second wires (I think) the header
   // per-cell has been disabled
   // connect(fermentableTable, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(fermentableCellSignal(const QPoint&)));
   QHeaderView* headerView = fermentableTable->horizontalHeader();
   headerView->setContextMenuPolicy(Qt::CustomContextMenu);
   connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(fermentableContextMenu(const QPoint&)));

   headerView = hopTable->horizontalHeader();
   headerView->setContextMenuPolicy(Qt::CustomContextMenu);
   connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(hopContextMenu(const QPoint&)));

   headerView = miscTable->horizontalHeader();
   headerView->setContextMenuPolicy(Qt::CustomContextMenu);
   connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(miscContextMenu(const QPoint&)));

   headerView = yeastTable->horizontalHeader();
   headerView->setContextMenuPolicy(Qt::CustomContextMenu);
   connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(yeastContextMenu(const QPoint&)));

   headerView = mashStepTableWidget->horizontalHeader();
   headerView->setContextMenuPolicy(Qt::CustomContextMenu);
   connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(mashStepContextMenu(const QPoint&)));

   connect( dialog_about->pushButton_donate, SIGNAL(clicked()), this, SLOT(openDonateLink()) );

   connect( equipmentComboBox, SIGNAL( activated(int) ), this, SLOT(updateRecipeEquipment()) );
   connect( equipmentButton, SIGNAL( clicked() ), this, SLOT(showEquipmentEditor()));

   connect( styleComboBox, SIGNAL( activated(int) ), this, SLOT(updateRecipeStyle()) );
   connect( styleButton, SIGNAL( clicked() ), this, SLOT(showStyleEditor()) );

   connect( mashComboBox, SIGNAL( activated(int) ), this, SLOT(updateRecipeMash()) );
   connect( mashButton, SIGNAL( clicked() ), mashEditor, SLOT( showEditor() ) );

   connect( lineEdit_name, SIGNAL( editingFinished() ), this, SLOT( updateRecipeName() ) );
   connect( lineEdit_batchSize, SIGNAL( editingFinished() ), this, SLOT( updateRecipeBatchSize() ) );
   connect( lineEdit_boilSize, SIGNAL( editingFinished() ), this, SLOT( updateRecipeBoilSize() ) );
   connect( lineEdit_boilTime, SIGNAL( editingFinished() ), this, SLOT( updateRecipeBoilTime() ) );
   connect( lineEdit_efficiency, SIGNAL( editingFinished() ), this, SLOT( updateRecipeEfficiency() ) );
   connect( pushButton_addFerm, SIGNAL( clicked() ), fermDialog, SLOT( show() ) );
   connect( pushButton_addHop, SIGNAL( clicked() ), hopDialog, SLOT( show() ) );
   connect( pushButton_addMisc, SIGNAL( clicked() ), miscDialog, SLOT( show() ) );
   connect( pushButton_addYeast, SIGNAL( clicked() ), yeastDialog, SLOT( show() ) );
   connect( pushButton_removeFerm, SIGNAL( clicked() ), this, SLOT( removeSelectedFermentable() ) );
   connect( pushButton_removeHop, SIGNAL( clicked() ), this, SLOT( removeSelectedHop() ) );
   connect( pushButton_removeMisc, SIGNAL( clicked() ), this, SLOT( removeSelectedMisc() ) );
   connect( pushButton_removeYeast, SIGNAL( clicked() ), this, SLOT( removeSelectedYeast() ) );
   connect( pushButton_editFerm, SIGNAL(clicked()), this, SLOT( editSelectedFermentable() ) );
   connect( pushButton_editMisc, SIGNAL( clicked() ), this, SLOT( editSelectedMisc() ) );
   connect( pushButton_editHop, SIGNAL( clicked() ), this, SLOT( editSelectedHop() ) );
   connect( pushButton_editYeast, SIGNAL( clicked() ), this, SLOT( editSelectedYeast() ) );
   connect( pushButton_editMash, SIGNAL( clicked() ), mashEditor, SLOT( showEditor() ) );
   connect( pushButton_addMashStep, SIGNAL( clicked() ), this, SLOT(addMashStep()) );
   connect( pushButton_removeMashStep, SIGNAL( clicked() ), this, SLOT(removeSelectedMashStep()) );
   connect( pushButton_editMashStep, SIGNAL( clicked() ), this, SLOT(editSelectedMashStep()) );
   connect( pushButton_mashWizard, SIGNAL( clicked() ), mashWizard, SLOT( show() ) );
   connect( pushButton_saveMash, SIGNAL( clicked() ), this, SLOT( saveMash() ) );
   connect( pushButton_mashDes, SIGNAL( clicked() ), mashDesigner, SLOT( show() ) );
   connect( pushButton_mashUp, SIGNAL( clicked() ), this, SLOT( moveSelectedMashStepUp() ) );
   connect( pushButton_mashDown, SIGNAL( clicked() ), this, SLOT( moveSelectedMashStepDown() ) );
   connect( pushButton_mashRemove, SIGNAL( clicked() ), this, SLOT( removeMash() ) );

   // drag and drop. maybe
   connect( tabWidget_recipeView, SIGNAL( setRecipe(Recipe*) ), this, SLOT( setRecipe(Recipe*)));
   connect( tabWidget_recipeView, SIGNAL( setEquipment(Equipment*) ), this, SLOT(droppedRecipeEquipment(Equipment*)));
   connect( tabWidget_recipeView, SIGNAL( setStyle(Style*) ), this, SLOT(droppedRecipeStyle(Style*)));
   connect( tabWidget_ingredients, SIGNAL( setFermentables(QList<Fermentable*>) ), this, SLOT(droppedRecipeFermentable(QList<Fermentable*>)));
   connect( tabWidget_ingredients, SIGNAL( setHops(QList<Hop*>) ), this, SLOT(droppedRecipeHop(QList<Hop*>)));
   connect( tabWidget_ingredients, SIGNAL( setMiscs(QList<Misc*>) ), this, SLOT(droppedRecipeMisc(QList<Misc*>)));
   connect( tabWidget_ingredients, SIGNAL( setYeasts(QList<Yeast*>) ), this, SLOT(droppedRecipeYeast(QList<Yeast*>)));

   // No connections from the database yet? Oh FSM, that probably means I'm
   // doing it wrong again.
   connect( &(Database::instance()), SIGNAL( deletedBrewNoteSignal(BrewNote*)), this, SLOT( closeBrewNote(BrewNote*)));
}

void MainWindow::setupShortCuts()
{
   actionNewRecipe->setShortcut(QKeySequence::New);
   actionCopy_Recipe->setShortcut(QKeySequence::Copy);
   actionSave->setShortcut(QKeySequence::Save);
   actionDeleteSelected->setShortcut(QKeySequence::Delete);
}

void MainWindow::deleteSelected()
{
   QModelIndexList selected; 
   BtTreeView* active = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());

   active->deleteSelected(active->selectionModel()->selectedRows());

   // This should be fixed to find the first nonfolder object in the tree
   QModelIndex first = active->first();
   if ( first.isValid() )
   {
      if (active->type(first) == BtTreeItem::RECIPE)
         setRecipe(first);
      setTreeSelection(first);
   }

}

void MainWindow::treeActivated(const QModelIndex &index)
{
   Equipment *kit;
   Fermentable *ferm;
   Hop* h;
   Misc *m;
   Yeast *y;
   Style *s;

   QObject* calledBy = sender();
   BtTreeView* active;

   // Not sure how this could happen, but better safe the sigsegv'd
   if ( calledBy == 0 )
      return;

   active = qobject_cast<BtTreeView*>(calledBy);

   // If the sender cannot be morphed into a BtTreeView object
   if ( active == 0 )
      return;

   switch( active->type(index))
   {
      case BtTreeItem::RECIPE:
         setRecipe(index);
         break;
      case BtTreeItem::EQUIPMENT:
         kit = active->equipment(index);
         if ( kit )
         {
            singleEquipEditor->setEquipment(kit);
            singleEquipEditor->show();
         }
         break;
      case BtTreeItem::FERMENTABLE:
         ferm = active->fermentable(index);
         if ( ferm )
         {
            fermEditor->setFermentable(ferm);
            fermEditor->show();
         }
         break;
      case BtTreeItem::HOP:
         h = active->hop(index);
         if (h)
         {
            hopEditor->setHop(h);
            hopEditor->show();
         }
         break;
      case BtTreeItem::MISC:
         m = active->misc(index);
         if (m)
         {
            miscEditor->setMisc(m);
            miscEditor->show();
         }
         break;
      case BtTreeItem::STYLE:
         s = active->style(index);
         if ( s )
         {
            singleStyleEditor->setStyle(s);
            singleStyleEditor->show();
         }
         break;
      case BtTreeItem::YEAST:
         y = active->yeast(index);
         if (y)
         {
            yeastEditor->setYeast(y);
            yeastEditor->show();
         }
         break;
      case BtTreeItem::BREWNOTE:
         setBrewNoteByIndex(index);
         break;
      case BtTreeItem::FOLDER:  // default behavior is fine, but no warning
         break;
      default:
         Brewtarget::log(Brewtarget::WARNING, QString("MainWindow::treeActivated Unknown type %1.").arg(treeView_recipe->type(index)));
   }
   treeView_recipe->setCurrentIndex(index);
}

void MainWindow::setBrewNoteByIndex(const QModelIndex &index)
{
   BrewNoteWidget* ni;

   BrewNote* bNote = treeView_recipe->brewNote(index);

   if ( ! bNote )
      return;
   // HERE
   // This is some clean up work. REMOVE FROM HERE TO THERE
   if ( bNote->projPoints() < 15 )
   {
      double pnts = bNote->projPoints();
      bNote->setProjPoints(pnts);
   }
   if ( bNote->effIntoBK_pct() < 10 )
   {
      bNote->calculateEffIntoBK_pct();
      bNote->calculateBrewHouseEff_pct();
   }
   // THERE

   Recipe* parent  = Database::instance().getParentRecipe(bNote);
   // I think this means a brew note for a different recipe has been selected.
   // We need to select that recipe, which will clear the current tabs
   if (  parent != recipeObs )
      setRecipe(parent);

   ni = findBrewNoteWidget(bNote);
   if ( ! ni ) 
   {
      ni = new BrewNoteWidget(tabWidget_recipeView);
      ni->setBrewNote(bNote);
   }

   tabWidget_recipeView->addTab(ni,bNote->brewDate_short());
   tabWidget_recipeView->setCurrentWidget(ni);

}

BrewNoteWidget* MainWindow::findBrewNoteWidget(BrewNote* b)
{
   for (int i = 0; i < tabWidget_recipeView->count(); ++i)
   {
      if (tabWidget_recipeView->widget(i)->objectName() == "BrewNoteWidget")
      {
         BrewNoteWidget* ni = qobject_cast<BrewNoteWidget*>(tabWidget_recipeView->widget(i));
         if ( ni->isBrewNote(b) ) 
            return ni;
      }
   }
   return 0;
}

void MainWindow::setBrewNote(BrewNote* bNote)
{
   QString tabname;
   BrewNoteWidget* ni = findBrewNoteWidget(bNote);

   if ( ni ) 
   {
      tabWidget_recipeView->setCurrentWidget(ni);
      return;
   }

   ni = new BrewNoteWidget(tabWidget_recipeView);
   ni->setBrewNote(bNote);

   tabWidget_recipeView->addTab(ni,bNote->brewDate_short());
   tabWidget_recipeView->setCurrentWidget(ni);
}

void MainWindow::setRecipe(const QModelIndex &index)
{
   Recipe *rec = treeView_recipe->recipe(index);
   if( rec )
      setRecipe(rec);
}

// Can handle null recipes.
void MainWindow::setRecipe(Recipe* recipe)
{
   int tabs = 0;
   // Don't like void pointers.
   if( recipe == 0 )
      return;

   // Make sure this MainWindow is paying attention...
   if( recipeObs )
      disconnect( recipeObs, 0, this, 0 );
   recipeObs = recipe;
   
   recStyle = recipe->style();
   recEquip = recipe->equipment();

   if( recStyle )
   {
      styleRangeWidget_og->setPreferredRange(Brewtarget::displayRange(recStyle, tab_recipe, "og", Brewtarget::GRAVITY ));
      styleRangeWidget_fg->setPreferredRange(Brewtarget::displayRange(recStyle, tab_recipe, "fg", Brewtarget::GRAVITY ));

      styleRangeWidget_abv->setPreferredRange(recStyle->abvMin_pct(), recStyle->abvMax_pct());
      styleRangeWidget_ibu->setPreferredRange(recStyle->ibuMin(), recStyle->ibuMax());
      styleRangeWidget_srm->setPreferredRange(Brewtarget::displayRange(recStyle, tab_recipe, "color_srm", Brewtarget::COLOR ));
   }
   
   // Reset all previous recipe shit.
   fermTableModel->observeRecipe(recipe);
   hopTableModel->observeRecipe(recipe);
   miscTableModel->observeRecipe(recipe);
   yeastTableModel->observeRecipe(recipe);
   mashStepTableModel->setMash(recipeObs->mash());

   // Clean out any brew notes
   tabWidget_recipeView->setCurrentIndex(0);
   // Start closing from the right (highest index) down. Anything else dumps
   // core in the most unpleasant of fashions
   tabs = tabWidget_recipeView->count() - 1;
   for (int i = tabs; i >= 0; --i)
   {
      if (tabWidget_recipeView->widget(i)->objectName() == "BrewNoteWidget")
         tabWidget_recipeView->removeTab(i);
   }
 
   // Tell some of our other widgets to observe the new recipe.
   mashWizard->setRecipe(recipe);
   brewDayScrollWidget->setRecipe(recipe);
   equipmentListModel->observeRecipe(recipe);
   recipeFormatter->setRecipe(recipe);
   ogAdjuster->setRecipe(recipe);
   recipeExtrasWidget->setRecipe(recipe);
   mashDesigner->setRecipe(recipe);
   equipmentButton->setRecipe(recipe);
   singleEquipEditor->setEquipment(recEquip);
   styleButton->setRecipe(recipe);
   singleStyleEditor->setStyle(recStyle);

   mashEditor->setMash(recipeObs->mash());
   mashEditor->setEquipment(recEquip);

   mashButton->setMash(recipeObs->mash());
   recipeScaler->setRecipe(recipeObs);

   // If you don't connect this late, every previous set of an attribute
   // causes this signal to be slotted, which then causes showChanges() to be
   // called.
   connect( recipeObs, SIGNAL(changed(QMetaProperty,QVariant)), this, SLOT(changed(QMetaProperty,QVariant)) );
   showChanges();
}

void MainWindow::changed(QMetaProperty prop, QVariant value)
{
   QString propName(prop.name());
   
   if( propName == "equipment" )
   {
      Equipment* newRecEquip = qobject_cast<Equipment*>(BeerXMLElement::extractPtr(value));
      recEquip = newRecEquip;
      
      singleEquipEditor->setEquipment(recEquip);
   }
   else if( propName == "style" )
   {
      //recStyle = recipeObs->style();
      recStyle = qobject_cast<Style*>(BeerXMLElement::extractPtr(value));
      singleStyleEditor->setStyle(recStyle);
      
   }

   showChanges(&prop);
}

void MainWindow::updateGravitySlider(QString attribute, RangedSlider* slider, double max)
{
   unitDisplay dispUnit = (unitDisplay)Brewtarget::option(attribute, noUnit, tab_recipe, Brewtarget::UNIT).toInt();

   if ( dispUnit == noUnit ) 
      dispUnit = Brewtarget::usePlato ? displayPlato : displaySg;

   slider->setPreferredRange(Brewtarget::displayRange(recStyle, tab_recipe, attribute, Brewtarget::GRAVITY));
   slider->setRange(         Brewtarget::displayRange(tab_recipe, attribute, 1.000, max, Brewtarget::GRAVITY ));
   if ( dispUnit == displayPlato )
   {
      slider->setPrecision(1);
      slider->setTickMarks(2,5);
   }
   else
   {
      slider->setPrecision(3);
      slider->setTickMarks(0.010, 2);
   }
}

void MainWindow::updateColorSlider(QString attribute, RangedSlider* slider)
{
   unitDisplay dispUnit = (unitDisplay)Brewtarget::option(attribute, noUnit, tab_recipe, Brewtarget::UNIT).toInt();

   if ( dispUnit == noUnit ) 
      dispUnit = Brewtarget::colorUnit == Brewtarget::SRM ? displaySrm : displayEbc;

   slider->setPreferredRange(Brewtarget::displayRange(recStyle, tab_recipe, attribute,Brewtarget::COLOR));
   slider->setRange(Brewtarget::displayRange(tab_recipe, attribute, 1, 44, Brewtarget::COLOR) );
   slider->setTickMarks( dispUnit == displaySrm ? 10 : 40, 2);

}

void MainWindow::showChanges(QMetaProperty* prop)
{
   if( recipeObs == 0 )
      return;

   bool updateAll = (prop == 0);
   QString propName;

   if( prop )
   {
      propName = prop->name();
   }
   
   lineEdit_name->setText(recipeObs->name());
   lineEdit_batchSize->setText(Brewtarget::displayAmount(recipeObs, tab_recipe, "batchSize_l", Units::liters));
   lineEdit_boilSize->setText(Brewtarget::displayAmount(recipeObs, tab_recipe, "boilSize_l", Units::liters));
   lineEdit_efficiency->setText(Brewtarget::displayAmount(recipeObs, tab_recipe, "efficiency_pct", 0,0));
   lineEdit_boilTime->setText(Brewtarget::displayAmount(recipeObs, tab_recipe, "boilTime_min", Units::minutes));
   lineEdit_name->setCursorPosition(0);
   lineEdit_batchSize->setCursorPosition(0);
   lineEdit_boilSize->setCursorPosition(0);
   lineEdit_efficiency->setCursorPosition(0);
   lineEdit_boilTime->setCursorPosition(0);
   
   label_calcBatchSize->setText(Brewtarget::displayAmount(recipeObs,tab_recipe, "finalVolume_l", Units::liters));
   label_calcBoilSize->setText(Brewtarget::displayAmount(recipeObs, tab_recipe, "boilVolume_l", Units::liters));

   // Color manipulation
   if( 0.95*recipeObs->batchSize_l() <= recipeObs->finalVolume_l() && recipeObs->finalVolume_l() <= 1.05*recipeObs->batchSize_l() )
      label_calcBatchSize->setPalette(numPalette_good);
   else if( recipeObs->finalVolume_l() < 0.95*recipeObs->batchSize_l() )
      label_calcBatchSize->setPalette(numPalette_tooLow);
   else
      label_calcBatchSize->setPalette(numPalette_tooHigh);
   
   if( 0.95*recipeObs->boilSize_l() <= recipeObs->boilVolume_l() && recipeObs->boilVolume_l() <= 1.05*recipeObs->boilSize_l() )
      label_calcBoilSize->setPalette(numPalette_good);
   else if( recipeObs->boilVolume_l() < 0.95* recipeObs->boilSize_l() )
      label_calcBoilSize->setPalette(numPalette_tooLow);
   else
      label_calcBoilSize->setPalette(numPalette_tooHigh);
   
   label_boilSG->setText(Brewtarget::displayOG(recipeObs,tab_recipe,"boilGrav",false));

   updateGravitySlider("og", styleRangeWidget_og, 1.120);
   styleRangeWidget_og->setValue(Brewtarget::displayOG(recipeObs,tab_recipe,"og",false).toDouble());

   QPair<QString, BeerXMLElement*> fg("fg",recipeObs);
   QPair<QString, BeerXMLElement*> og("og", recipeObs);

   updateGravitySlider("fg", styleRangeWidget_fg, 1.03);
   styleRangeWidget_fg->setValue(Brewtarget::displayFG(fg, og, tab_recipe,false).toDouble());

   styleRangeWidget_abv->setValue(recipeObs->ABV_pct());
   styleRangeWidget_ibu->setValue(recipeObs->IBU());

   /* Colors need the same basic treatment as gravity */
   updateColorSlider("color_srm", styleRangeWidget_srm);
   styleRangeWidget_srm->setValue(Brewtarget::displayColor(recipeObs,tab_recipe,"color_srm",false).toDouble());

   ibuGuSlider->setValue(recipeObs->IBU()/((recipeObs->og()-1)*1000));
   label_calories->setText( QString("%1").arg(recipeObs->calories(),0,'f',0) );

   // See if we need to change the mash in the table.
   if( (updateAll && recipeObs->mash()) ||
       (propName == "mash" &&
       recipeObs->mash()) )
   {
      mashStepTableModel->setMash(recipeObs->mash());
   }
}

void MainWindow::updateRecipeName()
{
   if( recipeObs == 0 || ! lineEdit_name->isModified())
      return;
   
   recipeObs->setName(lineEdit_name->text());
}

void MainWindow::updateRecipeStyle()
{
   if( recipeObs == 0 )
      return;

   QModelIndex proxyIndex( styleProxyModel->index(styleComboBox->currentIndex(),0) );
   QModelIndex sourceIndex( styleProxyModel->mapToSource(proxyIndex) );
   Style* selected = styleListModel->at(sourceIndex.row());
   if( selected )
   {
      Database::instance().addToRecipe( recipeObs, selected );

      styleRangeWidget_og->setPreferredRange( Brewtarget::displayRange(selected, tab_recipe, "og", Brewtarget::GRAVITY ));
      styleRangeWidget_fg->setPreferredRange( Brewtarget::displayRange(selected, tab_recipe, "fg", Brewtarget::GRAVITY ));

      styleRangeWidget_abv->setPreferredRange(selected->abvMin_pct(), selected->abvMax_pct());
      styleRangeWidget_ibu->setPreferredRange(selected->ibuMin(), selected->ibuMax());
      styleRangeWidget_srm->setPreferredRange(selected->colorMin_srm(), selected->colorMax_srm());
   }
}

void MainWindow::updateRecipeMash()
{
   if( recipeObs == 0 )
      return;

   Mash* selected = mashListModel->at(mashComboBox->currentIndex());
   if( selected )
   {
      Database::instance().addToRecipe( recipeObs, selected );
      mashEditor->setMash(recipeObs->mash());
      mashButton->setMash(recipeObs->mash());
   }
}

void MainWindow::updateRecipeEquipment()
{
  droppedRecipeEquipment(equipmentListModel->at(equipmentComboBox->currentIndex()));
}

void MainWindow::droppedRecipeEquipment(Equipment *kit)
{
   if( recipeObs == 0 )
      return;

   // equip may be null.
   if( kit == 0 )
      return;

   // Notice that we are using a copy from the database.
   Database::instance().addToRecipe(recipeObs,kit);
   equipmentButton->setEquipment(kit);

   // Keep the mash tun weight and specific heat up to date.
   Mash* m = recipeObs->mash();
   if( m )
   {
      m->setTunWeight_kg( kit->tunWeight_kg() );
      m->setTunSpecificHeat_calGC( kit->tunSpecificHeat_calGC() );
   }

   if( QMessageBox::question(this,
                             tr("Equipment request"),
                             tr("Would you like to set the batch size, boil size and time to that requested by the equipment?"),
                             QMessageBox::Yes,
                             QMessageBox::No)
        == QMessageBox::Yes
     )
   {
      recipeObs->setBatchSize_l( kit->batchSize_l() );
      recipeObs->setBoilSize_l( kit->boilSize_l() );
      recipeObs->setBoilTime_min( kit->boilTime_min() );
      mashEditor->setEquipment(kit);
   }
}

void MainWindow::droppedRecipeStyle(Style* style)
{
   if ( ! recipeObs )
      return;
   Database::instance().addToRecipe( recipeObs, style);
   styleButton->setStyle( style );
}

// Well, aint this a kick in the pants. Apparently I can't template a slot
void MainWindow::droppedRecipeFermentable(QList<Fermentable*>ferms)
{
   if ( ! recipeObs )
      return;

   if ( tabWidget_ingredients->currentWidget() != fermentableTab )
      tabWidget_ingredients->setCurrentWidget(fermentableTab);
   Database::instance().addToRecipe(recipeObs, ferms);
}

void MainWindow::droppedRecipeHop(QList<Hop*>hops)
{
   if ( ! recipeObs )
      return;

   if ( tabWidget_ingredients->currentWidget() != hopsTab )
      tabWidget_ingredients->setCurrentWidget(hopsTab);
   Database::instance().addToRecipe(recipeObs, hops);
}

void MainWindow::droppedRecipeMisc(QList<Misc*>miscs)
{
   if ( ! recipeObs )
      return;

   if ( tabWidget_ingredients->currentWidget() != miscTab )
      tabWidget_ingredients->setCurrentWidget(miscTab);
   Database::instance().addToRecipe(recipeObs, miscs);
}

void MainWindow::droppedRecipeYeast(QList<Yeast*>yeasts)
{
   if ( ! recipeObs )
      return;

   if ( tabWidget_ingredients->currentWidget() != yeastTab )
      tabWidget_ingredients->setCurrentWidget(yeastTab);
   Database::instance().addToRecipe(recipeObs, yeasts);
}

void MainWindow::updateRecipeBatchSize()
{
   unitDisplay dispUnit;
   if( recipeObs == 0 || ! lineEdit_batchSize->isModified() )
      return;
 
   dispUnit  = (unitDisplay)Brewtarget::option("batchsize_L", noUnit,tab_recipe,Brewtarget::UNIT).toInt();
   recipeObs->setBatchSize_l( Brewtarget::volQStringToSI(lineEdit_batchSize->text(),dispUnit) );
}

void MainWindow::updateRecipeBoilSize()
{
   unitDisplay dispUnit;
   if( recipeObs == 0 || ! lineEdit_boilSize->isModified() )
      return;
 
   dispUnit  = (unitDisplay)Brewtarget::option("boilsize_L", noUnit,tab_recipe,Brewtarget::UNIT).toInt();
   recipeObs->setBoilSize_l( Brewtarget::volQStringToSI(lineEdit_boilSize->text(), dispUnit) );
}

void MainWindow::updateRecipeBoilTime()
{
   double boilTime = 0.0;
   Equipment* kit;

   if( recipeObs == 0 || ! lineEdit_boilTime->isModified() )
      return;
 
   kit = recipeObs->equipment();
   boilTime = Brewtarget::timeQStringToSI( lineEdit_boilTime->text() );
   
   // Here, we rely on a signal/slot connection to propagate the equipment
   // changes to recipeObs->boilTime_min and maybe recipeObs->boilSize_l
   // NOTE: This works because kit is the recipe's equipment, not the generic
   // equipment in the recipe drop down.
   if( kit )
      kit->setBoilTime_min(boilTime);
   else
      recipeObs->setBoilTime_min(boilTime);
}

void MainWindow::updateRecipeEfficiency()
{
   if( recipeObs == 0 || ! lineEdit_efficiency->isModified() )
      return;
   
   recipeObs->setEfficiency_pct( lineEdit_efficiency->text().toDouble() );
}

void MainWindow::addFermentableToRecipe(Fermentable* ferm)
{
   recipeObs->addFermentable(ferm);
   fermTableModel->addFermentable(ferm);
}

void MainWindow::addHopToRecipe(Hop *hop)
{
   recipeObs->addHop(hop);
   hopTableModel->addHop(hop);
}

void MainWindow::addMiscToRecipe(Misc* misc)
{
   recipeObs->addMisc(misc);
   miscTableModel->addMisc(misc);
}

void MainWindow::addYeastToRecipe(Yeast* yeast)
{
   recipeObs->addYeast(yeast);
   yeastTableModel->addYeast(yeast);
}

void MainWindow::exportRecipe()
{
   QFile* outFile;
   QDomDocument doc;

   if( recipeObs == 0 )
      return;
  
   outFile = openForWrite();
   if ( ! outFile ) 
      return;

   QTextStream out(outFile);

   QString xmlHead = QString("version=\"1.0\" encoding=\"ISO-8859-1\"");

   // Create the headers to make other BeerXML parsers happy
   QDomProcessingInstruction inst = doc.createProcessingInstruction("xml", xmlHead);
   QDomComment beerxml = doc.createComment("BeerXML generated by brewtarget");

   doc.appendChild(inst);
   doc.appendChild(beerxml);

   QDomElement recipes = doc.createElement("RECIPES"); // The root element.
   doc.appendChild(recipes);
   Database::instance().toXml( recipeObs, doc, recipes );
   
   // QString::toLatin1 returns an ISO 8859-1 stream.
   out << doc.toString().toLatin1();
   
   outFile->close();
   delete outFile;
}

Recipe* MainWindow::currentRecipe()
{
   return recipeObs;
}

Fermentable* MainWindow::selectedFermentable()
{
   QModelIndexList selected = fermentableTable->selectionModel()->selectedIndexes();
   QModelIndex modelIndex, viewIndex;
   int row, size, i;

   size = selected.size();
   if( size == 0 )
      return 0;

   // Make sure only one row is selected.
   viewIndex = selected[0];
   row = viewIndex.row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return 0;
   }

   modelIndex = fermTableProxy->mapToSource(viewIndex);
   Fermentable* ferm = fermTableModel->getFermentable(modelIndex.row());

   return ferm;
}

Hop* MainWindow::selectedHop()
{
   QModelIndexList selected = hopTable->selectionModel()->selectedIndexes();
   QModelIndex modelIndex, viewIndex;
   int row, size, i;

   size = selected.size();
   if( size == 0 )
      return 0;

   // Make sure only one row is selected.
   viewIndex = selected[0];
   row = viewIndex.row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return 0;
   }

   modelIndex = hopTableProxy->mapToSource(viewIndex);

   Hop* h = hopTableModel->getHop(modelIndex.row());

   return h;
}

Misc* MainWindow::selectedMisc()
{
   QModelIndexList selected = miscTable->selectionModel()->selectedIndexes();
   QModelIndex modelIndex, viewIndex;
   int row, size, i;

   size = selected.size();
   if( size == 0 )
      return 0;

   // Make sure only one row is selected.
   viewIndex = selected[0];
   row = viewIndex.row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return 0;
   }

   modelIndex = miscTableProxy->mapToSource(viewIndex);

   Misc* m = miscTableModel->getMisc(modelIndex.row());

   return m;
}

Yeast* MainWindow::selectedYeast()
{
   QModelIndexList selected = yeastTable->selectionModel()->selectedIndexes();
   QModelIndex modelIndex, viewIndex;
   int row, size, i;

   size = selected.size();
   if( size == 0 )
      return 0;

   // Make sure only one row is selected.
   viewIndex = selected[0];
   row = viewIndex.row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return 0;
   }

   modelIndex = yeastTableProxy->mapToSource(viewIndex);

   Yeast* y = yeastTableModel->getYeast(modelIndex.row());

   return y;
}

void MainWindow::removeSelectedFermentable()
{
   Fermentable* f = selectedFermentable();
   if( f == 0 )
      return;

   fermTableModel->removeFermentable(f);
   recipeObs->removeFermentable(f);
}

void MainWindow::editSelectedFermentable()
{
   Fermentable* f = selectedFermentable();
   if( f == 0 )
      return;

   fermEditor->setFermentable(f);
   fermEditor->show();
}

void MainWindow::editSelectedMisc()
{
   Misc* m = selectedMisc();
   if( m == 0 )
      return;

   miscEditor->setMisc(m);
   miscEditor->show();
}

void MainWindow::editSelectedHop()
{
   Hop* h = selectedHop();
   if( h == 0 )
      return;

   hopEditor->setHop(h);
   hopEditor->show();
}

void MainWindow::editSelectedYeast()
{
   Yeast* y = selectedYeast();
   if( y == 0 )
      return;

   yeastEditor->setYeast(y);
   yeastEditor->show();
}

void MainWindow::removeSelectedHop()
{
   Hop* hop = selectedHop();
   if( hop == 0 )
      return;

   hopTableModel->removeHop(hop);
   recipeObs->removeHop(hop);
}

void MainWindow::removeSelectedMisc()
{
   Misc* misc = selectedMisc();
   if( misc == 0 )
      return;

   miscTableModel->removeMisc(misc);
   recipeObs->removeMisc(misc);
}

void MainWindow::removeSelectedYeast()
{
   Yeast* yeast = selectedYeast();
   if( yeast == 0 )
      return;

   yeastTableModel->removeYeast(yeast);
   recipeObs->removeYeast(yeast);
}

void MainWindow::newRecipe()
{
   QString name = QInputDialog::getText(this, tr("Recipe name"),
                                          tr("Recipe name:"));
   QVariant defEquipKey = Brewtarget::option("defaultEquipmentKey", -1); 
   QObject* selection = sender();

   if( name.isEmpty() )
      return;

   Recipe* newRec = Database::instance().newRecipe();

   // Set the following stuff so everything appears nice
   // and the calculations don't divide by zero... things like that.
   newRec->setName(name);
   newRec->setBatchSize_l(18.93); // 5 gallons
   newRec->setBoilSize_l(23.47);  // 6.2 gallons
   newRec->setEfficiency_pct(70.0);

   if ( defEquipKey != -1 )
   {
      Equipment* e = Database::instance().equipment(defEquipKey.toInt());
      if ( e )
      {
         Database::instance().addToRecipe(newRec, e);
         newRec->setBatchSize_l( e->batchSize_l() );
         newRec->setBoilSize_l( e->boilSize_l() );
         newRec->setBoilTime_min( e->boilTime_min() );
      }
   }

   // a new recipe will be put in a folder if you right click on a recipe or
   // folder. Otherwise, it goes into the main window?
   if ( selection ) {
      BtTreeView* sent = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());
      if ( sent ) 
      {
         QModelIndexList indexes = sent->selectionModel()->selectedRows();
         // This is a little weird. There is an edge case where nothing is
         // selected and you click the big blue + button.
         if ( indexes.size() > 0 ) 
         {
            if ( sent->type(indexes.at(0)) == BtTreeItem::RECIPE ) 
            {
               Recipe* foo = sent->recipe(indexes.at(0));

               if ( foo && ! foo->folder().isEmpty()) 
                  newRec->setFolder( foo->folder() );
            }
            else if ( sent->type(indexes.at(0)) == BtTreeItem::FOLDER ) 
            {
               BtFolder* foo = sent->folder(indexes.at(0));
               if ( foo )
                  newRec->setFolder( foo->fullPath() );
            }
         }
      }
   }
   setTreeSelection(treeView_recipe->findElement(newRec));
   setRecipe(newRec);
}

void MainWindow::newFolder()
{
   QString dPath;
   QModelIndexList indexes;
   QModelIndex starter;
   // get the currently active tree
   BtTreeView* active = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());

   if (! active )
      return;

   indexes = active->selectionModel()->selectedRows();
   starter = indexes.at(0);

   // Where to start from
   dPath = active->folderName(starter);

   QString name = QInputDialog::getText(this, tr("Folder name"),
                                          tr("Folder name:"),
                                           QLineEdit::Normal, dPath);
   // User clicks cancel
   if (name.isEmpty())
      return;
   // Do some input validation here.

   // Nice little builtin to collapse leading and following white space
   name = name.simplified(); 
   if ( name.isEmpty() )
   {
      QMessageBox::critical( this, tr("Bad Name"), 
                             tr("A folder name must have at least one non-whitespace character in it"));
      return;
   }

   if ( name.split("/", QString::SkipEmptyParts).isEmpty() )
   {
      QMessageBox::critical( this, tr("Bad Name"), tr("A folder name must have at least one non-/ character in it"));
      return;
   }
   active->addFolder(name);
}

void MainWindow::renameFolder()
{
   BtTreeView* active = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());
   BtFolder* victim;
   QModelIndexList indexes;
   QModelIndex starter;

   // If the sender cannot be morphed into a BtTreeView object
   if ( active == 0 )
      return;

   // I don't think I can figure out what the behavior will be if you select
   // many items
   indexes = active->selectionModel()->selectedRows();
   starter = indexes.at(0);

   // The item to be renamed
   // Don't rename anything other than a folder
   if ( active->type(starter) != BtTreeItem::FOLDER )
      return;

   victim = active->folder(starter);
   QString newName = QInputDialog::getText(this, tr("Folder name"), tr("Folder name:"),
                                           QLineEdit::Normal, victim->name());

   // User clicks cancel
   if (newName.isEmpty())
      return;
   // Do some input validation here.

   // Nice little builtin to collapse leading and following white space
   newName = newName.simplified(); 
   if ( newName.isEmpty() )
   {
      QMessageBox::critical( this, tr("Bad Name"), 
                             tr("A folder name must have at least one non-whitespace character in it"));
      return;
   }

   if ( newName.split("/", QString::SkipEmptyParts).isEmpty() )
   {
      QMessageBox::critical( this, tr("Bad Name"), tr("A folder name must have at least one non-/ character in it"));
      return;
   }
   newName = victim->path() % "/" % newName;

   // Delgate this work to the tree.
   active->renameFolder(victim,newName);
}

void MainWindow::setTreeSelection(QModelIndex item)
{
   BtTreeView *active = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());

   if (! item.isValid())
      return;

   if ( active == 0 )
      active = qobject_cast<BtTreeView*>(treeView_recipe);

   // Couldn't cast the active item to a BtTreeView
   if ( active == 0 )
      return;

   QModelIndex parent = active->parent(item);

   active->setCurrentIndex(item);
   if ( active->type(parent) == BtTreeItem::FOLDER && !
         active->isExpanded(parent) )
      active->setExpanded(parent,true);
   active->scrollTo(item,QAbstractItemView::PositionAtCenter);
   
}
// reduces the inventory by the selected recipes 
void MainWindow::reduceInventory(){
   
   QModelIndexList indexes = treeView_recipe->selectionModel()->selectedRows();
      QModelIndex bIndex;

      foreach(QModelIndex selected, indexes)
      {
         Recipe*   rec   = treeView_recipe->recipe(selected);
         if( rec == 0 ){
            //try the parent recipe
            rec = treeView_recipe->recipe(treeView_recipe->parent(selected));
            if( rec == 0 ){
               continue;
            }
         }

         // Make sure everything is properly set and selected
         if( rec != recipeObs )
            setRecipe(rec);   

      int i = 0;
      //reduce fermentables
      QList<Fermentable*> flist = rec->fermentables();
      if(flist.size() > 0){
         for( i = 0; static_cast<int>(i) < flist.size(); ++i )
         {
            double newVal=flist[i]->inventory() - flist[i]->amount_kg();   
            newVal = (newVal < 0) ? 0 : newVal;
            flist[i]->setInventoryAmount(newVal); 
         }
      }
      
      //reduce misc
      QList<Misc*> mlist = rec->miscs();
      if(mlist.size() > 0){
         for( i = 0; static_cast<int>(i) < mlist.size(); ++i )
         {
            double newVal=mlist[i]->inventory() - mlist[i]->amount();   
            newVal = (newVal < 0) ? 0 : newVal;
            mlist[i]->setInventoryAmount(newVal);
         }  
      }
      //reduce hops
      QList<Hop*> hlist = rec->hops(); 
      if(hlist.size() > 0){
         for( i = 0; static_cast<int>(i) < hlist.size(); ++i )
         {
            double newVal = hlist[i]->inventory() - hlist[i]->amount_kg();    
            newVal = (newVal < 0) ? 0 : newVal;
            hlist[i]->setInventoryAmount(newVal);
         }  
      }
      //reduce yeast
      QList<Yeast*> ylist = rec->yeasts();   
      if(ylist.size() > 0){
         for( i = 0; static_cast<int>(i) < ylist.size(); ++i )
         {
            //Yeast inventory is done by quanta not amount
            int newVal = ylist[i]->inventory() - 1;   
            newVal = (newVal < 0) ? 0 : newVal; 
            ylist[i]->setInventoryQuanta(newVal);
         } 
      }
   }
   
}

// Need to make sure the recipe tree is active, I think
void MainWindow::newBrewNote()
{
   QModelIndexList indexes = treeView_recipe->selectionModel()->selectedRows();
   QModelIndex bIndex;

   foreach(QModelIndex selected, indexes)
   {
      Recipe*   rec   = treeView_recipe->recipe(selected);
      QModelIndex newItem;

      if( rec == 0 )
         continue;

      // Make sure everything is properly set and selected
      if( rec != recipeObs )
         setRecipe(rec);

      BrewNote* bNote = Database::instance().newBrewNote(rec);
      bNote->populateNote(rec);
      bNote->setBrewDate();

      setBrewNote(bNote);

      bIndex = treeView_recipe->findElement(bNote);
      if ( bIndex.isValid() )
         setTreeSelection(bIndex);
   }
}
  
void MainWindow::reBrewNote()
{
   QModelIndexList indexes = treeView_recipe->selectionModel()->selectedRows();
   foreach(QModelIndex selected, indexes)
   {
      BrewNote* old   = treeView_recipe->brewNote(selected);
      Recipe* rec     = treeView_recipe->recipe(treeView_recipe->parent(selected));

      if (! old || ! rec)
         return;

      BrewNote* bNote = Database::instance().newBrewNote(old);
      bNote->setBrewDate();

      if (rec != recipeObs)
         setRecipe(rec);

      setBrewNote(bNote);

      setTreeSelection(treeView_recipe->findElement(bNote));
   }
}

void MainWindow::brewItHelper(){
   newBrewNote();
   reduceInventory();
}

void MainWindow::brewAgainHelper(){
   reBrewNote();
   reduceInventory();
}

void MainWindow::backup()
{
   QString dir = QFileDialog::getExistingDirectory(this, tr("Backup Database"));
   
   bool success = Database::backupToDir(dir);
   
   if( ! success )
      QMessageBox::warning( this, tr("Oops!"), tr("Could not copy the files for some reason."));
}

void MainWindow::restoreFromBackup()
{
   if( QMessageBox::question(
          this,
          tr("A Warning"),
          tr("This will obliterate your current set of recipes and ingredients. Do you want to continue?"),
          QMessageBox::Yes, QMessageBox::No
       )
       == QMessageBox::No
   )
   {
      return;
   }
   
   QString restoreDbFile = QFileDialog::getOpenFileName(this, tr("Choose File"), "", tr("SQLite (*.sqlite)"));
   bool success = Database::restoreFromFile(restoreDbFile);
   
   if( ! success )
      QMessageBox::warning( this, tr("Oops!"), tr("For some reason, the operation failed.") );
   else
      QMessageBox::information(this, tr("Restart"), tr("Please restart Brewtarget."));
   //TODO: do this without requiring restarting :)
}

// Imports all the recipes from a file into the database.
void MainWindow::importFiles()
{
   if ( ! fileOpener->exec() )
      return;
   
   foreach( QString filename, fileOpener->selectedFiles() )
   {
      if ( ! Database::instance().importFromXML(filename) )
         importMsg();
   }
   
   showChanges();
}

bool MainWindow::verifyImport(QString tag, QString name)
{
   return QMessageBox::question(this, tr("Import %1?").arg(tag), tr("Import %1?").arg(name),
                                QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
}

void MainWindow::addMashStep()
{
   Mash* mash;
   if( recipeObs != 0 && recipeObs->mash() != 0 )
   {
      mash = recipeObs->mash();
   }
   else
   {
      QMessageBox::information(this, tr("No mash"), tr("Trying to add a mash step without a mash. Please create a mash first.") );
      return;
   }

   MashStep* step = Database::instance().newMashStep(mash);
   mashStepEditor->setMashStep(step);
   mashStepEditor->setVisible(true);
}

void MainWindow::removeSelectedMashStep()
{
   Mash* mash = recipeObs == 0 ? 0 : recipeObs->mash();
   if( mash == 0 )
      return;
   
   QModelIndexList selected = mashStepTableWidget->selectionModel()->selectedIndexes();
   int row, size, i;

   size = selected.size();
   if( size == 0 )
      return;

   // Make sure only one row is selected.
   row = selected[0].row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return;
   }

   MashStep* step = mashStepTableModel->getMashStep(row);
   Database::instance().removeFrom(mash,step);
}

void MainWindow::moveSelectedMashStepUp()
{
   QModelIndexList selected = mashStepTableWidget->selectionModel()->selectedIndexes();
   int row, size, i;
   
   size = selected.size();
   if( size == 0 )
      return;
   
   // Make sure only one row is selected.
   row = selected[0].row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return;
   }
   
   // Make sure we can actually move it up.
   if( row < 1 )
      return;
   
   MashStep* m1 = mashStepTableModel->getMashStep(row);
   MashStep* m2 = mashStepTableModel->getMashStep(row-1);
   Database::instance().swapMashStepOrder(m1,m2);
}

void MainWindow::moveSelectedMashStepDown()
{
   QModelIndexList selected = mashStepTableWidget->selectionModel()->selectedIndexes();
   int row, size, i;
   
   size = selected.size();
   if( size == 0 )
      return;
   
   // Make sure only one row is selected.
   row = selected[0].row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return;
   }
   
   // Make sure it's not the last row so we can move it down.
   if( row >= mashStepTableModel->rowCount() - 1 )
      return;
   
   MashStep* m1 = mashStepTableModel->getMashStep(row);
   MashStep* m2 = mashStepTableModel->getMashStep(row+1);
   Database::instance().swapMashStepOrder(m1,m2);
}

void MainWindow::editSelectedMashStep()
{
   if( ! ( recipeObs || recipeObs->mash() ) )
      return;

   QModelIndexList selected = mashStepTableWidget->selectionModel()->selectedIndexes();
   int row, size, i;

   size = selected.size();
   if( size == 0 )
      return;

   // Make sure only one row is selected.
   row = selected[0].row();
   for( i = 1; i < size; ++i )
   {
      if( selected[i].row() != row )
         return;
   }

   MashStep* step = mashStepTableModel->getMashStep(row);
   mashStepEditor->setMashStep(step);
   mashStepEditor->setVisible(true);
}

void MainWindow::removeMash()
{
   Mash *m = mashButton->mash();

   if( m == 0)
      return;
   //due to way this is designed, we can't have a NULL mash, so
   //we need to remove all the mash steps and then remove the mash
   //from the database.
   //remove from db

   m->removeAllMashSteps();
   Database::instance().remove(m);
   
   Mash* defaultMash = Database::instance().newMash(recipeObs);
   mashStepTableModel->setMash(defaultMash);
   
   //remove from combobox handled automatically by qt
   mashButton->setMash(defaultMash);

}

void MainWindow::save()
{
   Database::instance().saveDatabase();
}

void MainWindow::closeEvent(QCloseEvent* /*event*/)
{
   Brewtarget::saveSystemOptions();
   Brewtarget::setOption("geometry", saveGeometry());
   Brewtarget::setOption("windowState", saveState());
   if ( recipeObs )
      Brewtarget::setOption("recipeKey", recipeObs->key());
   
   // After unloading the database, can't make any more queries to it, so first
   // make the main window disappear so that redraw events won't inadvertently
   // cause any more queries.
   setVisible(false);
   
   // Ask the user if they want to save changes, only if the dirty bit has
   // been thrown
   if( Database::instance().isDirty() &&
       QMessageBox::question(this,
          QObject::tr("Save Database Changes"),
          QObject::tr("Would you like to save the changes you made?"),
          QMessageBox::Yes | QMessageBox::No,
          QMessageBox::Yes)
      == QMessageBox::Yes)
   {
      Database::instance().unload(true);
   }
   else
   {
      Database::instance().unload(false);
   }
}

void MainWindow::copyRecipe()
{
   QString name = QInputDialog::getText( this, tr("Copy Recipe"), tr("Enter a unique name for the copy.") );
   
   if( name.isEmpty() )
      return;
   
   Recipe* newRec = Database::instance().newRecipe(recipeObs); // Create a deep copy.
   newRec->setName(name);
}

void MainWindow::setMashToCurrentlySelected()
{
   if( recipeObs == 0 )
      return;

   Mash* selected = mashListModel->at(mashComboBox->currentIndex());
   if( selected )
   {
      Database::instance().newMash(selected);
      mashButton->setMash(selected);
   }
}

void MainWindow::saveMash()
{
   if( recipeObs == 0 || recipeObs->mash() == 0 )
      return;

   Mash* mash = recipeObs->mash();
   // Ensure the mash has a name.
   if( mash->name() == "" )
   {
      QMessageBox::information( this, tr("Oops!"), tr("Please give your mash a name before saving.") );
      return;
   }
   
   // NOTE: should NOT displace recipeObs' current mash.
   Mash* newMash = Database::instance().newMash(mash, false);
   // NOTE: need to set the display to true for the saved, named mash to work
   newMash->setDisplay(true);
   mashButton->setMash(newMash);

}

void MainWindow::openDonateLink()
{
   QDesktopServices::openUrl(QUrl("http://sourceforge.net/project/project_donations.php?group_id=249733"));
}

void MainWindow::openManual()
{
   QDesktopServices::openUrl(QUrl::fromLocalFile(Brewtarget::getDataDir()+"brewtarget-manual.html"));
}

// One print function to rule them all. Now we just need to make the menuing
// system make sense
void MainWindow::print()
{
   QObject* selection = sender();

   if ( selection == actionRecipePrint || selection == actionBrewdayPrint )
   {
      QPrintDialog printerDialog(printer, this);
      selection == actionRecipePrint ?  recipeFormatter->print( printer, &printerDialog, RecipeFormatter::PRINT) :
                                        brewDayScrollWidget->print( printer, &printerDialog, BrewDayScrollWidget::PRINT);
   }
   else if ( selection == actionRecipePreview )
   {
      recipeFormatter->print(printer, 0, RecipeFormatter::PREVIEW);
   }
   else if ( selection == actionBrewdayPreview )
   {
      brewDayScrollWidget->print(printer, 0, RecipeFormatter::PREVIEW);
   }
   else if ( selection == actionRecipeHTML || selection == actionBrewdayHTML)
   {
      QFile* outfile = openForWrite(tr("HTML files (*.html)"), QString("html"));

      if (! outfile )
         return;
      selection == actionRecipeHTML ? recipeFormatter->print(printer, 0, RecipeFormatter::HTML, outfile) : 
                                      brewDayScrollWidget->print(printer, 0, BrewDayScrollWidget::HTML, outfile);
      delete outfile;
   }
}

// We build the menus at start up time.  This just needs to exec the proper
// menu.  
void MainWindow::contextMenu(const QPoint &point)
{
   QObject* calledBy = sender();
   BtTreeView* active;
   QModelIndex selected;
   QMenu* tempMenu;

   // Not sure how this could happen, but better safe the sigsegv'd
   if ( calledBy == 0 )
      return;

   active = qobject_cast<BtTreeView*>(calledBy);

   // If the sender cannot be morphed into a BtTreeView object
   if ( active == 0 )
      return;

   selected = active->indexAt(point);
   if (! selected.isValid())
      return;

   tempMenu = active->contextMenu(selected);

   if (tempMenu)
      tempMenu->exec(active->mapToGlobal(point));
}

void MainWindow::setupContextMenu()
{

   treeView_recipe->setupContextMenu(this,this);
   treeView_equip->setupContextMenu(this,equipEditor);

   treeView_ferm->setupContextMenu(this,fermDialog);
   treeView_hops->setupContextMenu(this,hopDialog);
   treeView_misc->setupContextMenu(this,miscDialog);
   treeView_style->setupContextMenu(this,singleStyleEditor);
   treeView_yeast->setupContextMenu(this,yeastDialog);

   // TreeView for clicks, both double and right
   connect( treeView_recipe, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_recipe, SIGNAL(customContextMenuRequested( const QPoint& )), this, SLOT(contextMenu(const QPoint &)));

   connect( treeView_equip, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_equip, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenu(const QPoint &)));

   connect( treeView_ferm, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_ferm, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenu(const QPoint &)));

   connect( treeView_hops, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_hops, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenu(const QPoint &)));

   connect( treeView_misc, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_misc, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenu(const QPoint &)));

   connect( treeView_yeast, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_yeast, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenu(const QPoint &)));

   connect( treeView_style, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(treeActivated(const QModelIndex &)));
   connect( treeView_style, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenu(const QPoint &)));

}

void MainWindow::copyThis(Recipe *rec)
{
   QString name = QInputDialog::getText( this, tr("Copy %1").arg(rec->name()), tr("Enter a unique name for the copy of %1.").arg(rec->name()) );

   if( name.isEmpty() )
      return;

   Recipe* newRec = Database::instance().newRecipe(rec); // Create a deep copy.
   newRec->setName(name);
}

void MainWindow::copyThis(Equipment *kit)
{
   QString name = QInputDialog::getText( this, tr("Copy Equipment"), tr("Enter a unique name for the copy of %1.").arg(kit->name()) );

   if( name.isEmpty() )
      return;

   Equipment* newKit = Database::instance().newEquipment(kit); // Create a deep copy.
   newKit->setName(name);
}

void MainWindow::copyThis(Fermentable *ferm)
{
   QString name = QInputDialog::getText( this, tr("Copy Fermentable"), tr("Enter a unique name for the copy of %1.").arg(ferm->name()) );

   if( name.isEmpty() )
      return;

   Fermentable* newFerm = Database::instance().newFermentable(ferm); // Create a deep copy.
   newFerm->setName(name);
}

void MainWindow::copyThis(Hop *hop)
{
   QString name = QInputDialog::getText( this, tr("Copy Hop"), tr("Enter a unique name for the copy of %1.").arg(hop->name()));

   if( name.isEmpty() )
      return;

   Hop* newHop = Database::instance().newHop(hop); // Create a deep copy.
   newHop->setName(name);
}

void MainWindow::copyThis(Misc *misc)
{
   QString name = QInputDialog::getText( this, tr("Copy Miscellaneous"), tr("Enter a unique name for the copy of %1.").arg(misc->name()) );

   if( name.isEmpty() )
      return;

   Misc* newMisc = Database::instance().newMisc(misc); // Create a deep copy.
   newMisc->setName(name);
}

void MainWindow::copyThis(Yeast *yeast)
{
   QString name = QInputDialog::getText( this, tr("Copy Yeast"), tr("Enter a unique name for the copy of %1.").arg(yeast->name()) );

   if( name.isEmpty() )
      return;

   Yeast* newYeast = Database::instance().newYeast(yeast); // Create a deep copy.
   newYeast->setName(name);
}

void MainWindow::copyThis(Style *style)
{
   QString name = QInputDialog::getText( this, tr("Copy Style"), tr("Enter a unique name for the copy of %1.").arg(style->name()) );

   if( name.isEmpty() )
      return;

   Style* newStyle = Database::instance().newStyle(style); // Create a deep copy.
   newStyle->setName(name);
}

void MainWindow::copySelected()
{
   BtTreeView* active = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());

   QList<QModelIndex>::const_iterator at, end;
   QModelIndex above;
   QList<Recipe*> copyRec;
   QList<Equipment*> copyKit;
   QList<Fermentable*> copyFerm;
   QList<Hop*> copyHop;
   QList<Misc*> copyMisc;
   QList<Style*> copyStyle;
   QList<Yeast*> copyYeast;

   if ( active == 0 )
      return;

   const QModelIndexList selected = active->selectionModel()->selectedRows();

   // We need to process them all before we get the names, because adding new things does mess
   // up the indexes.  This ... is not gonna be pretty.
   for(at = selected.begin(),end = selected.end();at < end;++at)
   {
      switch(active->type(*at))
      {
         case BtTreeItem::RECIPE:
            if ( *at == active->findElement(0) )
               continue;
            copyRec.append(active->recipe(*at));
            break;
         case BtTreeItem::EQUIPMENT:
            if ( *at == active->findElement(0) )
               continue;
            copyKit.append(active->equipment(*at));
            break;
         case BtTreeItem::FERMENTABLE:
            if ( *at == active->findElement(0) )
               continue;
            copyFerm.append(active->fermentable(*at));
            break;
         case BtTreeItem::HOP:
            if ( *at == active->findElement(0) )
               continue;
            copyHop.append(active->hop(*at));
            break;
         case BtTreeItem::MISC:
            if ( *at == active->findElement(0) )
               continue;
            copyMisc.append(active->misc(*at));
            break;
         case BtTreeItem::STYLE:
            if ( *at == active->findElement(0) )
               continue;
            copyStyle.append(active->style(*at));
            break;
         case BtTreeItem::YEAST:
            if ( *at == active->findElement(0) )
               continue;
            copyYeast.append(active->yeast(*at));
            break;
            // No Brewnote, because it just doesn't make sense
         default:
            Brewtarget::log(Brewtarget::WARNING, QString("MainWindow::copySelected Unknown type: %1").arg(active->type(*at)));
      }
   }

   for(int i = 0; i < copyRec.count(); ++i)
      copyThis(copyRec.at(i));
   for(int i = 0; i < copyKit.count(); ++i)
      copyThis(copyKit.at(i));
   for(int i = 0; i < copyFerm.count(); ++i)
      copyThis(copyFerm.at(i));
   for(int i = 0; i < copyMisc.count(); ++i)
      copyThis(copyMisc.at(i));
   for(int i = 0; i < copyStyle.count(); ++i)
      copyThis(copyStyle.at(i));
   for(int i = 0; i < copyYeast.count(); ++i)
      copyThis(copyYeast.at(i));


   above = active->first();
   if ( active->type(above) == BtTreeItem::RECIPE )
      setRecipe(above);
   setTreeSelection(above);
}

QFile* MainWindow::openForWrite( QString filterStr, QString defaultSuff)
{
   const char* filename;
   QFile* outFile = new QFile();

   fileSaver->setFilter( filterStr );
   fileSaver->setDefaultSuffix( defaultSuff );

   if( fileSaver->exec() )
   {
      filename = fileSaver->selectedFiles()[0].toAscii();
      outFile->setFileName(filename);

      if( ! outFile->open(QIODevice::WriteOnly | QIODevice::Truncate) )
      {
         Brewtarget::logW(QString("MainWindow::openForWrite Could not open %1 for writing.").arg(filename));
         outFile = 0;
      }
   }
   else
     outFile = 0;

   return outFile;
}

void MainWindow::exportSelected()
{
   BtTreeView* active = qobject_cast<BtTreeView*>(tabWidget_Trees->currentWidget()->focusWidget());
   QModelIndexList selected;
   QList<QModelIndex>::const_iterator at,end;
   QDomDocument doc;
   QFile* outFile;
   QDomElement root,dbase,recipe;
   bool didRecipe = false;

   if ( active == 0 ) 
      return;

   selected = active->selectionModel()->selectedRows();
   if( selected.count() == 0 )
      return;

   outFile = openForWrite(); 
   if ( !outFile )
      return;

   QTextStream out(outFile);

   QString xmlHead = QString("version=\"1.0\" encoding=\"%1\"").arg(QTextCodec::codecForLocale()->name().data());

   // Create the headers to make other BeerXML parsers happy
   QDomProcessingInstruction inst = doc.createProcessingInstruction("xml", xmlHead);
   QDomComment beerxml = doc.createComment("BeerXML generated by brewtarget");

   doc.appendChild(inst);
   doc.appendChild(beerxml);

   // We need to handle the recipes separate from the normal database
   // elements.  All recipes live under the RECIPES tag, whereas the
   // equipment, hops, etc. go under DATABASE.
   dbase = doc.createElement("DATABASE"); 
   recipe = doc.createElement("RECIPES"); 

   for(at = selected.begin(),end = selected.end(); at < end; ++at)
   {
      QModelIndex selection = *at;
      int type = active->type(selection);

      switch(type)
      {
         case BtTreeItem::RECIPE:
            Database::instance().toXml( treeView_recipe->recipe(selection), doc, recipe);
            didRecipe = true;
            break;
         case BtTreeItem::EQUIPMENT:
            Database::instance().toXml( treeView_equip->equipment(selection), doc, dbase);
            break;
         case BtTreeItem::FERMENTABLE:
            Database::instance().toXml( treeView_ferm->fermentable(selection), doc, dbase);
            break;
         case BtTreeItem::HOP:
            Database::instance().toXml( treeView_hops->hop(selection), doc, dbase);
            break;
         case BtTreeItem::MISC:
            Database::instance().toXml( treeView_misc->misc(selection), doc, dbase);
            break;
         case BtTreeItem::STYLE:
            Database::instance().toXml( treeView_style->style(selection), doc, dbase);
            break;
         case BtTreeItem::YEAST:
            Database::instance().toXml( treeView_yeast->yeast(selection), doc, dbase);
            break;
      }
   }

   if ( didRecipe )
      doc.appendChild(recipe);
   else
      doc.appendChild(dbase);

   out << doc.toString();
   
   outFile->close();
   delete outFile;
}

void MainWindow::updateDatabase()
{
   QString otherDb;
   QMessageBox::StandardButton but;
   
   // Tell user what's about to happen.
   but = QMessageBox::question( this,
                          tr("Database Update"),
                          tr("You are about to update the current database with another one. "
                             "This may make changes to (but will not delete) some of your ingredients. "
                             "It will not modify any of your recipes. "
                             "Continue?"),
                          QMessageBox::Yes | QMessageBox::No,
                          QMessageBox::No );
   if( but == QMessageBox::No )
      return;
   
   // Select the db to merge with.
   otherDb = QFileDialog::getOpenFileName( this,
                                           tr("Select Database File"),
                                           Brewtarget::getUserDataDir(),
                                           tr("Brewtarget Database (*.sqlite)") );
   
   // Merge.
   Database::instance().updateDatabase( otherDb );
}

void MainWindow::finishCheckingVersion()
{
   QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
   if( reply == 0 )
      return;
   
   QString remoteVersion(reply->readAll());
   
   // If there is an error, just return.
   if( reply->error() != QNetworkReply::NoError )
      return;
   
   // If the remote version is newer...
   if( !remoteVersion.startsWith(VERSIONSTRING) )
   {
      // ...and the user wants to download the new version...
      if( QMessageBox::information(this,
                                   QObject::tr("New Version"),
                                   QObject::tr("Version %1 is now available. Download it?").arg(remoteVersion),
                                   QMessageBox::Yes | QMessageBox::No,
                                   QMessageBox::Yes) == QMessageBox::Yes )
      {
         // ...take them to the website.
         QDesktopServices::openUrl(QUrl("http://www.brewtarget.org/download.html"));
      }
      else // ... and the user does NOT want to download the new version...
      {
         // ... and they want us to stop bothering them...
         if( QMessageBox::question(this,
                                   QObject::tr("New Version"),
                                   QObject::tr("Stop bothering you about new versions?"),
                                   QMessageBox::Yes | QMessageBox::No,
                                   QMessageBox::Yes) == QMessageBox::Yes)
         {
            // ... tell brewtarget to stop bothering the user about the new version.
            Brewtarget::checkVersion = false;
         }
      }
   }
   else // The current version is newest so...
   {
      // ...tell brewtarget to bother users about future new versions.
      // This means that when a user downloads the new version, this
      // variable will always get reset to true.
      Brewtarget::checkVersion = true;
   }
}

void MainWindow::redisplayLabel(QString field)
{
   // There is a lot of magic going on in the showChanges(). I can either
   // duplicate that magic or I can just call showChanges().
   showChanges();
}

void MainWindow::fermentableContextMenu(const QPoint &point)
{
   QObject* calledBy = sender();
   QHeaderView* hView = qobject_cast<QHeaderView*>(calledBy);

   int selected = hView->logicalIndexAt(point);
   unitDisplay currentUnit;
   unitScale  currentScale;

   // Since we need to call generateVolumeMenu() two different ways, we need
   // to figure out the currentUnit and Scale here

   currentUnit  = fermTableModel->displayUnit(selected);
   currentScale = fermTableModel->displayScale(selected);
   
   QMenu* menu;
   QAction* invoked;

   switch(selected)
   {
      case FERMINVENTORYCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale); 
         break;
      case FERMAMOUNTCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale); 
         break;
      case FERMCOLORCOL:
         menu = Brewtarget::setupColorMenu(this,currentUnit); 
         break;
      default:
         return;
   }

   invoked = menu->exec(hView->mapToGlobal(point));
   if ( invoked == 0 )
      return;

   QWidget* pMenu = invoked->parentWidget();
   if ( pMenu == menu )
      fermTableModel->setDisplayUnit(selected,(unitDisplay)invoked->data().toInt());
   else
      fermTableModel->setDisplayScale(selected,(unitScale)invoked->data().toInt());

   showChanges();
}

void MainWindow::hopContextMenu(const QPoint &point)
{
   QObject* calledBy = sender();
   QHeaderView* hView = qobject_cast<QHeaderView*>(calledBy);

   int selected = hView->logicalIndexAt(point);
   unitDisplay currentUnit;
   unitScale  currentScale;

   // Since we need to call generateVolumeMenu() two different ways, we need
   // to figure out the currentUnit and Scale here

   currentUnit  = hopTableModel->displayUnit(selected);
   currentScale = hopTableModel->displayScale(selected);
   
   QMenu* menu;
   QAction* invoked;

   switch(selected)
   {
      case HOPINVENTORYCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale); 
         break;
      case HOPAMOUNTCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale); 
         break;
      default:
         return;
   }

   invoked = menu->exec(hView->mapToGlobal(point));
   if ( invoked == 0 )
      return;

   QWidget* pMenu = invoked->parentWidget();
   if ( pMenu == menu )
      hopTableModel->setDisplayUnit(selected,(unitDisplay)invoked->data().toInt());
   else
      hopTableModel->setDisplayScale(selected,(unitScale)invoked->data().toInt());

   showChanges();
}

void MainWindow::mashStepContextMenu(const QPoint &point)
{
   QObject* calledBy = sender();
   QHeaderView* hView = qobject_cast<QHeaderView*>(calledBy);

   int selected = hView->logicalIndexAt(point);
   unitDisplay currentUnit;
   unitScale  currentScale;

   // Since we need to call generateVolumeMenu() two different ways, we need
   // to figure out the currentUnit and Scale here

   currentUnit  = mashStepTableModel->displayUnit(selected);
   currentScale = mashStepTableModel->displayScale(selected);
   
   QMenu* menu;
   QAction* invoked;

   switch(selected)
   {
      case MASHSTEPAMOUNTCOL:
         menu = Brewtarget::setupVolumeMenu(this,currentUnit, currentScale); 
         break;
      case MASHSTEPTEMPCOL:
      case MASHSTEPTARGETTEMPCOL:
         menu = Brewtarget::setupTemperatureMenu(this,currentUnit);
         break;
      default:
         return;
   }

   invoked = menu->exec(hView->mapToGlobal(point));
   if ( invoked == 0 )
      return;

   QWidget* pMenu = invoked->parentWidget();
   if ( pMenu == menu )
      mashStepTableModel->setDisplayUnit(selected,(unitDisplay)invoked->data().toInt());
   else
      mashStepTableModel->setDisplayScale(selected,(unitScale)invoked->data().toInt());

   showChanges();
}

void MainWindow::miscContextMenu(const QPoint &point)
{
   QObject* calledBy = sender();
   QHeaderView* hView = qobject_cast<QHeaderView*>(calledBy);

   int selected = hView->logicalIndexAt(point);
   unitDisplay currentUnit;
   unitScale  currentScale;

   // Since we need to call generateVolumeMenu() two different ways, we need
   // to figure out the currentUnit and Scale here

   currentUnit  = miscTableModel->displayUnit(selected);
   currentScale = miscTableModel->displayScale(selected);
   
   QMenu* menu;
   QAction* invoked;

   switch(selected)
   {
      case MISCINVENTORYCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale); 
         break;
      case MISCAMOUNTCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale, false); 
         break;
      default:
         return;
   }

   invoked = menu->exec(hView->mapToGlobal(point));
   if ( invoked == 0 )
      return;

   miscTableModel->setDisplayUnit(selected,(unitDisplay)invoked->data().toInt());

   showChanges();
}

void MainWindow::yeastContextMenu(const QPoint &point)
{
   QObject* calledBy = sender();
   QHeaderView* hView = qobject_cast<QHeaderView*>(calledBy);

   int selected = hView->logicalIndexAt(point);
   unitDisplay currentUnit;
   unitScale  currentScale;

   // Since we need to call generateVolumeMenu() two different ways, we need
   // to figure out the currentUnit and Scale here

   currentUnit  = yeastTableModel->displayUnit(selected);
   currentScale = yeastTableModel->displayScale(selected);
   
   QMenu* menu;
   QAction* invoked;

   switch(selected)
   {
      case YEASTAMOUNTCOL:
         menu = Brewtarget::setupMassMenu(this,currentUnit, currentScale, false); 
         break;
      default:
         return;
   }

   invoked = menu->exec(hView->mapToGlobal(point));
   if ( invoked == 0 )
      return;

   yeastTableModel->setDisplayUnit(selected,(unitDisplay)invoked->data().toInt());

   showChanges();
}

void MainWindow::showPitchDialog()
{
   // First, copy the current recipe og and volume.
   if( recipeObs )
   {
      pitchDialog->setWortVolume_l( recipeObs->finalVolume_l() );
      pitchDialog->setWortGravity( recipeObs->og() );
      pitchDialog->calculate();
   }
   
   pitchDialog->show();
}

void MainWindow::showEquipmentEditor()
{
   if ( ! recipeObs->equipment() )
   {
      QMessageBox::warning( this, tr("No equipment"), tr("You must select or define an equipment profile first."));
   }
   else
   {
      singleEquipEditor->show();
   }
}

void MainWindow::showStyleEditor()
{
   if ( ! recipeObs->style() )
   {
      QMessageBox::warning( this, tr("No style"), tr("You must select a style first."));
   }
   else
   {
      singleStyleEditor->show();
   }
}

void MainWindow::convertedMsg()
{

   QMessageBox msgBox;
   QDir dir(Brewtarget::getUserDataDir());

   msgBox.setText( tr("The database has been converted/upgraded."));
   msgBox.setInformativeText( tr("The original XML files can be found in ") + Brewtarget::getUserDataDir() + "obsolete");
   msgBox.exec();

}

void MainWindow::importMsg()
{
   QMessageBox msgBox;
   msgBox.setText( tr("The import contained invalid beerXML. It has been imported, but please make certain it makes sense."));
   msgBox.exec();
}

void MainWindow::changeBrewDate()
{
   QModelIndexList indexes = treeView_recipe->selectionModel()->selectedRows();
   QDateTime newDate;

   foreach(QModelIndex selected, indexes)
   {
      BrewNote* target = treeView_recipe->brewNote(selected);

      // No idea how this could happen, but I've seen stranger things
      if ( ! target ) 
         continue;

      // Pop the calendar, get the date. 
      if ( btDatePopup->exec() == QDialog::Accepted )
      {
         newDate = btDatePopup->selectedDate();
         target->setBrewDate(newDate);

         // If this note is open in a tab
         BrewNoteWidget* ni = findBrewNoteWidget(target);
         if ( ni )
         {
            tabWidget_recipeView->setTabText(tabWidget_recipeView->indexOf(ni), target->brewDate_short());
            return;
         }
      }
   }
}

void MainWindow::fixBrewNote()
{
   QModelIndexList indexes = treeView_recipe->selectionModel()->selectedRows();
   QDateTime newDate;

   foreach(QModelIndex selected, indexes)
   {
      BrewNote* target = treeView_recipe->brewNote(selected);

      // No idea how this could happen, but I've seen stranger things
      if ( ! target ) 
         continue;

      Recipe* noteParent = treeView_recipe->recipe( treeView_recipe->parent(selected));

      if ( ! noteParent ) 
         continue;

      target->recalculateEff(noteParent);
   }
}

void MainWindow::closeBrewNote(BrewNote* b)
{
   Recipe* parent = Database::instance().getParentRecipe(b);

   // If this isn't the focused recipe, do nothing because there are no tabs
   // to close.
   if ( parent != recipeObs )
      return;

   BrewNoteWidget* ni = findBrewNoteWidget(b);

   if ( ni )
      tabWidget_recipeView->removeTab( tabWidget_recipeView->indexOf(ni));

   return;

}
