qbrew-0.4.1/0000755000175100017510000000000011016417354011600 5ustar daviddavidqbrew-0.4.1/translations/0000755000175100017510000000000011016417430014314 5ustar daviddavidqbrew-0.4.1/translations/qbrew_de.ts0000644000175100017510000024024011016417216016460 0ustar daviddavid AlcoholTool Alcohol Tool Alcohol Alkohol 99.9% Enter the measured original gravity of your batch Geben Sie den gemessenen Stammwürzegehalt Ihrer Charge an Enter the measured final gravity of your batch Geben Sie die gemessene Enddichte Ihrer Charge an Alcohol by Weight Alkoholgewicht Alcohol by Volume Alkoholvolumen &Final gravity &Enddichte &Original gravity &Stammwürzegehalt - Alcohol Tool CalcConfig Calc Configuration Calculation Options <p>There are two color calculations that QBrew can use. The default method uses the <b>Daniels</b> formula. By selecting this check box, you can change the calculation to use the <b>Morey</b> formula.</p> Use &Morey color calculation Alt+M <p>There are two bitterness calculations that QBrew can use. The default method is to use the <b>Rager</b> formula. By selecting this check box, you can change the calculation to use the <b>Tinseth</b> formula.</p> Use &Tinseth bitterness calculation Alt+T Mash &efficiency <p>Enter the mash efficiency for your brew house</p> <p>Select the units of measurement you wish to use. Your choices are "<b>Metric</b>" and "<b>US</b>".</p> Measurement &units Steep &yield Configure - Configure &General &Calculations &Recipe DatabaseTool Database Tool Datenbank-Werkzeug &File &Datei Save Speichern Save the database Datenbank speichern Ctrl+S Ctrl+S Quit Beenden Close the database tool Datenbank-Werkzeug schließen Ctrl+Q Ctrl+Q &Grains &Getreide &Hops &Hopfen &Miscellaneous &Verschiedenes &Styles <p>Unable to save the database.You do not have permission to write to %1 <p>Unable to save the database.Error in saving %1 GeneralConfig Form Appearance Options &Look and feel <p>You may select a different look and feel for the application. This will affect the appearance of the application's controls and widgets.</p> <p>Select this option to show the splash screen when the program starts.</p> &Show splash screen File Options <p>Select this option to automatically load the last file used in this program.</p> Load last &file <p>Selecting this will cause the program to automatically backup every file that you open and modify.</p> Enable auto&backup <p>This field will allow you to select the Autosave interval.</p> Minutes <p>Enables autosaving of files. If this is checked, files will be automatically saved at the selected interval.</p> Enable &autosave <p>Use this to set the number of files displayed in the <b>Open Recent...</b> menu</p> Number of &recent files GrainModel Grain Getreide Quantity Menge Extract Extrakt Color Farbe Use Verwendung - Delete? Do you wish to remove this entry? Generic Weight Type HelpViewer Help Viewer[*] &Navigate &File &Datei Main Toolbar Hauptwerkzeugleiste &Backward Go to the previous page Alt+ &Forward Go to the next page &Home Go to the index page Ctrl+Home &Print... &Drucken ... Print the document Dokument drucken Ctrl+P Ctrl+P &Quit &Beenden Close the help viewer Anzeige der Hilfe schließen Ctrl+Q Ctrl+Q %1 Help - %2 Hop Pellet Plug Whole HopDelegate min minutes HopModel min minutes - Delete? Do you wish to remove this entry? Generic Hop Weight Alpha Time Type HydrometerTool Hydrometer Tool Hydrometer-Werkzeug Hydrometer Hydrometer 1.000 Corrected reading Korrigierter Wert Enter the reading obtained from your hydrometer Geben Sie den Wert ein, den Sie von Ihrem Hydrometer abgelesen haben Enter the temperature your hydrometer is calibrated for Geben Sie die Temperatur ein, für die Ihr Hydrometer kalibriert ist &Hydrometer reading Vom &Hydrometer abgelesener Wert &Calibrated temperature &Kalibrierte Temperatur &Sample temperature &Probentemperatur Enter the temperature of your sample Geben Sie die Temperatur Ihrer Probe ein - Hydrometer Tool IngredientPage Form ... Add ingredient Use this button to add a new ingredient to the recipe Remove ingredient Use this button to remove the currently selected ingredient IngredientView &Add Ingredient Ctrl+Insert Add a new ingredient &Remove Ingredient Ctrl+Delete Remove selected ingredient &Clear Selection Escape Clear selection MainWindow QBrew[*] &Help &Hilfe &Options &Optionen &Tools &Werkzeuge &File &Datei Open &Recent &Letzte Dateien Main Toolbar Hauptwerkzeugleiste &New &Neu Create a new recipe Neues Rezept erstellen Use this command to create a new recipe Verwenden Sie diesen Befehl, um ein neues Rezept zu erstellen Ctrl+N Ctrl+N &Open &Öffnen Open a recipe Rezept öffnen Use this command to open a recipe Verwenden Sie diesen Befehl, um ein Rezept zu öffnen Ctrl+O Ctrl+O &Save &Speichern Save the recipe Rezept speichern Use this command to save the current recipe Verwenden Sie diesen Befehl, um das aktuelle Rezept zu speichern Ctrl+S Ctrl+S Save &as... Speichern &als ... Save the recipe under a new name Rezept unter einem neuen Namen speichern &Print... &Drucken ... Print the recipe Rezept drucken Use this command to print the recipe Verwenden Sie diesen Befehl, um das Rezept zu drucken Ctrl+P Ctrl+P &Export... &Exportieren ... Export the recipe Rezept exportieren Export the recipe to a non-native format Exportieren des Rezepts in ein anderes Format &Quit &Beenden Quit the application Programm beenden Ctrl+Q Ctrl+Q &Statusbar &Statuszeile Toggle the statusbar Statuszeile an- und abschalten Enable or disable the Statusbar Statuszeile aktivieren oder deaktivieren &Configure... &Konfiguration ... Display the configuration dialog Anzeige des Konfigurationsdialogs &Contents &Inhalt Display the application handbook Anzeige des Programmhandbuchs F1 F1 &About... &Über ... Application information Programminformation Display application copyright and information Anzeige des Programm-Copyrights sowie weiterer Informationen &What's This? &Was ist das? Context help Kontexthilfe Display context sensitive help cursor Anzeige eines kontextsensitiven Hilfecursors Shift+F1 Shift+F1 &Alcohol Percentage... &Alkoholprozente ... Alcohol percentage calculator Berechnung der Alkoholprozente &Hydrometer Correction... &Hydrometer-Korrektur ... Hydrometer correction utility Werkzeug zur Temperaturkorrektur des Hydrometers &Database Editor... &Datenbank-Editor ... Database editor Datenbank-Editor Primer Starter Display a brewing primer Anzeige der Grundlagen des Brauens Print Pre&view... Preview the printed recipe Use this command to preview printing the recipe MiscModel - Delete? Do you wish to remove this entry? Generic Misc Quantity Menge Type Notes NoteView NoteView &Recipe Notes &Rezeptnotizen Recipe Notes Rezeptnotizen <p>Use this form to enter in any notes relating to the recipe, such as specific brewing procedures or awards.</p> <p>Verwenden Sie dieses Formular, um beliebige Notizen zu diesem Rezept einzugeben. Beispiele sind Brauprozeduren oder Preise.</p> &Batch Notes &Chargennotizen Batch Notes Chargennotizen <p>Use this form to enter in any notes relating to brewing batches of this recipe, such as brew and bottling dates.</p> <p>Verwenden Sie dieses Formular, um beliebige Notizen zu Brau-Chargen dieses Rezepts einzugeben. Beispiele sind Braudaten oder Flaschenabfüllungsdaten.</p> PreviewDialog Zoom in Zoom out Page setup Print QBrew splash.png /autosave. Canceled... Created new recipe Opening recipe... Open... Loaded recipe Load failed %1 was unable to make a backup of %2 Saved recipe %1 was unable to save %2 Error in saving recipe Saving recipe under new filename... Save As... Unable to save to %1 Saving aborted Export... Exported recipe Unable to export to %1 Error exporting recipe Export aborted Print preview... Printing... Configuring %1 ... Version <p align=center><small>Contributions by About Saved configuration Unable to load the recipe Unable to import the file %1 Autosaving recipe... - Save? <p>Do you wish to save your work first? - Overwrite? <p>Are you sure you want to overwrite the file "%1" Toggle the toolbar Enable or disable the Toolbar QObject A Homebrewer's Recipe Calculator Rezeptberechner für Hobbybrauer Usage: %1 [options] [file] Verwendung: %1 [Optionen] [Datei] Arguments file File to open Argumente Datei Zu öffnende Datei Options --help Print the command line options. --version Print the application version. Optionen --help Anzeige der Befehlszeilenoptionen. --version Anzeige der Programmversion. Invalid parameter "%1" Ungültiger Parameter "%1" QBrew files (*.qbrew) QBrew-Dateien (*.qbrew) Text files (*.txt) Textdateien (*.txt) HTML files (*.html *.htm) HTML-Dateien (*.html *.htm) BeerXML recipes (*.xml) BeerXML-Rezepte (*.xml) PDF files (*.pdf) PDF-Dateien (*.pdf) All files (*) Alle Dateien (*) untitled unbenannt Metric Metrisch US US Could not open data file Cannot read file %1: %2 Wrong file type for %1 Unsupported version %1 Cannot write file %1: %2 No recipes found in %1 Multiple recipes found. Please select one: Cannot process file %1 Generic Usage: %1 [options] [file] Arguments file File to open Options --help Print the command line options. --version Print the application version. Invalid parameter "%1" Recipe Recipe Style Sorte Brewer Brauer Batch Recipe Characteristics Recipe Gravity OG</td> Estimated FG FG</td> Recipe Bitterness IBU</td> Alcohol by Volume Alkoholvolumen %</td> Recipe Color SRM</td> Alcohol by Weight Alkoholgewicht Ingredients Quantity Menge Grain Getreide Type Use Verwendung Hop Time minutes</td> </tr> Misc Notes Recipe Notes Rezeptnotizen Batch Notes Chargennotizen Brewer: Style: Batch: Characteristics Recipe Gravity: OG Recipe Bitterness: IBU Recipe Color: SRM Estimated FG: Alcohol by Volume: % Alcohol by Weight: % minutes Recipe Notes: Batch Notes: Cannot read file %1: %2 Wrong file type for %1 Not a recipe for %1 Unsupported version %1 Cannot write file %1: %2 RecipeConfig Recipe Configuration Recipe Defaults &Recipe style &Batch size <p>Select the default recipe style you wish to use. You may always change an individual recipe's style, but this determines the default style for new recipes.</p> <p>Enter the default batch size you wish to use. You may always change an individual recipe's size, but this determines the default size for new recipes.</p> &Hop type <p>Select the default hops type you wish to use. You may always change an individual hop's form, but this determines the default form when adding new hops to a recipe.</p> StyleModel Generic - Delete? Do you wish to remove this entry? Style Sorte Min. OG Max. OG Min. FG Max. FG Min. IBU Max. IBU Min. SRM Max. SRM View View Recipe Style Rezeptstil <p>Use this control to select the style of recipe.</p> <p>This only affects the minimum and maximum values in the <em>characteristics</em> section</p> <p>Verwenden Sie diese Auswahl, um den Stil des Rezepts auszuwählen.</p> <p>Dies beeinflusst nur die Minimal- und Maximalwerte im <em>Eigenschaften</em>-Abschnitt</p> Mash Maische Recipe Title Titel des Rezepts <p>Use this field to enter the title of the recipe</p> <p>Verwenden Sie dieses Feld, um den Titel des Rezepts einzugeben.</p> Title Titel Batch size Chargenmenge <p>Use this control to enter the size of the recipe batch</p> <p>Verwenden Sie dieses Feld, um die Menge dieser Rezept-Charge einzugeben</p> Style Sorte Brewer Brauer Brewer Name Name des Brauers <p>Use this field to enter the name of the brewer</p> <p>Verwenden Sie dieses Feld, um den Namen des Brauers einzugeben.</p> Size Menge stylebox 1.000 Minimum Gravity: Minimaler Stammwürzegehalt: Recipe Gravity: Stammwürzegehalt des Rezepts: Maximum Gravity: Maximaler Stammwürzegehalt: 000 Minimum Bitterness: Minimale Bitterkeit: Recipe Bitterness: Bitterkeit des Rezepts: Maximum Bitterness: Maximale Bitterkeit: Maximum Color: Maximale Farbe: Minimum Color: Minimale Farbe: Recipe Color: Farbe des Rezepts: 00.0% Alcohol by Volume: Alkoholvolumen: Alcohol by Weight: Alkoholgewicht: Estimate FG: Geschätzte Enddichte: &Grains &Getreide &Hops &Hopfen &Miscellaneous &Verschiedenes &Notes &Notizen %1 characteristics Beer style characteristics, e.g. Generic ale Eigenschaften eines %1 %1% %1% 00.0° &Title &Style &Brewer Si&ze copyright Copyright 1999-2007 description A Homebrewer's Recipe Calculator Rezeptberechner für Hobbybrauer file filter QBrew files (*.qbrew) QBrew-Dateien (*.qbrew) Text files (*.txt) Textdateien (*.txt) HTML files (*.html *.htm) HTML-Dateien (*.html *.htm) BeerXML recipes (*.xml) BeerXML-Rezepte (*.xml) PDF files (*.pdf) PDF-Dateien (*.pdf) All files (*) Alle Dateien (*) file name untitled unbenannt grain Extract Extrakt Mashed Steeped Grain Getreide Adjunct Sugar Other message Ready misc Yeast Fining Herb Spice Flavor Additive Other recipe Extract Extrakt Partial Mash All Grain units Metric Metrisch US US qbrew-0.4.1/src/0000755000175100017510000000000011016417423012364 5ustar daviddavidqbrew-0.4.1/src/misc.h0000644000175100017510000000733211016417220013470 0ustar daviddavid/*************************************************************************** misc.h ------------------- A generic ingredient class ------------------- Copyright 1999-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef MISC_H #define MISC_H #include #include #include #include "quantity.h" class Misc { public: // misc strings static const QByteArray YEAST_STRING; static const QByteArray FINING_STRING; static const QByteArray HERB_STRING; static const QByteArray SPICE_STRING; static const QByteArray FLAVOR_STRING; static const QByteArray ADDITIVE_STRING; static const QByteArray OTHER_STRING; // default constructor Misc(); // full constructor Misc(const QString &name, const Quantity &quantity, const QString &typ, const QString ¬es); // copy constructor Misc(const Misc &m); // assignment operator Misc operator=(const Misc &m); // comparison operator bool operator==(const Misc &m) const; bool operator<(const Misc &m) const; // destructor ~Misc(); //get/set name const QString &name() const; void setName(const QString &name); // get/set quantity const Quantity &quantity() const; void setQuantity(const Quantity &quantity); // get/set type const QString &type() const; void setType(const QString &typ); // get/set notes const QString ¬es() const; void setNotes(const QString ¬es); // list of misc type static QStringList typeStringList(); private: QString name_; Quantity quantity_; QString type_; QString notes_; }; typedef QList MiscList; typedef QMap MiscMap; ////////////////////////////////////////////////////////////////////////////// // Inlined Methods inline bool Misc::operator<(const Misc &m) const { return (name_ < m.name_); } inline void Misc::setName(const QString &name) { name_ = name; } inline const QString &Misc::name() const { return name_; } inline void Misc::setQuantity(const Quantity &quantity) { quantity_ = quantity; } inline const Quantity &Misc::quantity() const { return quantity_; } inline void Misc::setType(const QString &typ) { type_ = typ; } inline const QString &Misc::type() const { return type_; } inline void Misc::setNotes(const QString ¬es) { notes_ = notes; } inline const QString &Misc::notes() const{ return notes_; } #endif // MISC_H qbrew-0.4.1/src/style.h0000644000175100017510000001045411016417220013674 0ustar daviddavid/*************************************************************************** style.h ------------------- AHA-like style class ------------------- Copyright 1999-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef STYLE_H #define STYLE_H #include #include #include class Style { public: // default constructor Style(); // constructor Style(const QString name, const double &oglow, const double &oghi, const double &fglow, const double &fghi, const int &ibulow, const int &ibuhi, const int &srmlow, const int &srmhi); // copy constructor Style(const Style &s); // operators Style operator=(const Style &s); bool operator==(const Style &s) const; bool operator<(const Style &s) const; // destructor ~Style(); // return name of style const QString &name() const; void setName(const QString &name); // return high end of OG double OGHi() const; void setOGHi(double hi); // return low end of OG double OGLow() const; void setOGLow(double lo); // return high end of FG double FGHi() const; void setFGHi(double hi); // return low end of FG double FGLow() const; void setFGLow(double lo); // return high end of IBU int IBUHi() const; void setIBUHi(int hi); // return low end of IBU int IBULow() const; void setIBULow(int lo); // return high end of SRM int SRMHi() const; void setSRMHi(int hi); // return low end of SRM int SRMLow() const; void setSRMLow(int lo); private: friend class StyleModel; QString name_; double oglow_; double oghi_; double fglow_; double fghi_; int ibulow_; int ibuhi_; int srmlow_; int srmhi_; }; typedef QList\n"; html += "\n\n"; html += "\n"; html += table.arg("header").arg("5 bgcolor=\"#CCCCCC\" width=\"100%\""); html += "\n" + header.arg(escape(title_)) + "\n"; html += "\n

\n\n"; // recipe table html += table.arg("recipe").arg(0); html += "\n\n"; html += th.arg(tr("Recipe")); html += "\n"; html += "" + escape(title_) + "\n"; html += "\n"; html += th.arg(tr("Style")); html += "\n"; html += "" + escape(style_.name()) + "\n"; html += "\n\n"; html += th.arg(tr("Brewer")); html += "\n"; html += "" + escape(brewer_) + "\n"; html += "\n"; html += th.arg(tr("Batch")); html += "\n"; html += "" + size_.toString(2) + "\n"; html += "\n\n" + th.arg(tr(method().toUtf8())); html += "\n\n\n

\n\n"; // characteristics table html += header.arg(tr("Recipe Characteristics")); html += table.arg("characteristics").arg(0); html += "\n\n"; html += th.arg(tr("Recipe Gravity")); html += "\n"; html += "" + QString::number(og_, 'f', 3) + tr(" OG\n"); html += "\n"; html += th.arg(tr("Estimated FG")); html += "\n"; html += "" + QString::number(FGEstimate(), 'f', 3) + tr(" FG\n"); html += "\n\n"; html += th.arg(tr("Recipe Bitterness")); html += "\n"; html += "" + QString::number(ibu_, 'f', 0) + tr(" IBU\n"); html += "\n"; html += th.arg(tr("Alcohol by Volume")); html += "\n"; html += "" + QString::number(ABV() * 100.0, 'f', 1) + tr("%\n"); html += "\n\n"; html += th.arg(tr("Recipe Color")); html += "\n"; html += "" + QString::number(srm_, 'f', 0) + DEGREE + tr(" SRM\n"); html += "\n"; html += th.arg(tr("Alcohol by Weight")); html += "\n"; html += "" + QString::number(ABW() * 100.0, 'f', 1) + tr("%\n"); html += "\n\n\n

\n\n"; // ingredients table html += header.arg(tr("Ingredients")); html += table.arg("ingredients").arg(0); html += "\n"; // grains html += "\n" + th.arg(tr("Quantity")); html += "\n"; html += th.arg(tr("Grain")); html += "\n"; html += th.arg(tr("Type")); html += "\n"; html += th.arg(tr("Use")); html += "\n\n"; foreach (Grain grain, grains_) { html += "\n" + grain.weight().toString(2) + "\n"; html += "\n"; html += "" + escape(grain.name()) + "\n"; html += "\n"; html += "" + grain.type() + "\n"; html += "\n"; html += "" + grain.use() + "\n\n\n"; } // hops html += "\n" + th.arg(tr("Quantity")); html += "\n"; html += th.arg(tr("Hop")); html += "\n"; html += th.arg(tr("Type")); html += "\n"; html += th.arg(tr("Time")); html += "\n\n"; foreach (Hop hop, hops_) { html += "\n" + hop.weight().toString(2) + "\n"; html += "\n"; html += "" + escape(hop.name()) + "\n"; html += "\n"; html += "" + hop.type() + "\n"; html += "\n"; html += "" + QString::number(hop.time()) + tr(" minutes\n\n\n"); } // misc ingredients html += "\n" + th.arg(tr("Quantity")); html += "\n"; html += th.arg(tr("Misc")); html += "\n"; html += th.arg(tr("Notes")); html += "\n\n"; foreach (Misc misc, miscs_) { html += "\n" + misc.quantity().toString(2) + "\n"; html += "\n"; html += "" + escape(misc.name()) + "\n"; html += "\n"; html += "" + misc.type() + "\n"; html += "\n"; html += "" + escape(misc.notes()) + "\n\n\n"; } html += "\n\n

\n\n"; // notes // TODO: using replace() might be dangerous if we ever use richtext in notes html += header.arg(tr("Recipe Notes")) + "\n"; html += "

" + escape(recipenotes_).replace('\n', "
\n") + "\n

\n

\n"; html += header.arg(tr("Batch Notes")) + "\n"; html += "

" + escape(batchnotes_).replace('\n', "
\n") + "\n

\n"; html += "\n\n"; return html; } ////////////////////////////////////////////////////////////////////////////// // exportText() // ------------ // Export recipe as text bool Recipe::exportText(const QString &filename) { if (!filename.isEmpty()) { QFile datafile(filename); if (!datafile.open(QFile::WriteOnly | QFile::Text)) { // error opening file qWarning() << "Error: Cannot open file" << filename; QMessageBox::warning(0, TITLE, QObject::tr("Cannot write file %1:\n%2") .arg(filename) .arg(datafile.errorString())); datafile.close(); return false; } QTextStream data(&datafile); QApplication::setOverrideCursor(Qt::WaitCursor); data << recipeText(); QApplication::restoreOverrideCursor(); datafile.close(); return true; } return false; } ////////////////////////////////////////////////////////////////////////////// // recipeText() // ------------ // Get the ascii text of the recipe for exporting const QString Recipe::recipeText() { // title stuff QString text = underline(title()); text += tr("Brewer: ") + brewer_ + '\n'; text += tr("Style: ") + style_.name() + '\n'; text += tr("Batch: ") + size_.toString(2); text += tr(method().toUtf8()); text += "\n\n"; // style stuff text += underline(tr("Characteristics")); text += tr("Recipe Gravity: ") + QString::number(og_, 'f', 3) + tr(" OG\n"); text += tr("Recipe Bitterness: ") + QString::number(ibu_, 'f', 0) + tr(" IBU\n"); text += tr("Recipe Color: ") + QString::number(srm_, 'f', 0) + DEGREE + tr(" SRM\n"); text += tr("Estimated FG: ") + QString::number(FGEstimate(), 'f', 3) + '\n'; text += tr("Alcohol by Volume: ") + QString::number(ABV() * 100.0, 'f', 1) + tr("%\n"); text += tr("Alcohol by Weight: ") + QString::number(ABW() * 100.0, 'f', 1) + tr("%\n\n"); // ingredients text += underline(tr("Ingredients")); // grains // using a map will sort the grains QMultiMap gmap; foreach (Grain grain, grains_) gmap.insert(grain.name(), grain); foreach (Grain grain, gmap.values()) { text += grain.name().leftJustified(30, ' '); text += grain.weight().toString(2) + ", "; text += grain.type() + ", "; text += grain.use() + '\n'; } text += '\n'; // hops // using a map will sort the hops QMultiMap hmap; foreach (Hop hop, hops_) hmap.insert(hop.name(), hop); foreach (Hop hop, hmap.values()) { text += hop.name().leftJustified(30, ' '); text += hop.weight().toString(2) + ", "; text += hop.type() + ", "; text += QString::number(hop.time()) + tr(" minutes\n"); } text += '\n'; // misc ingredients // using a map will sort the ingredients QMultiMap mmap; foreach (Misc misc, miscs_) mmap.insert(misc.name(), misc); foreach (Misc misc, mmap.values()) { text += misc.name().leftJustified(30, ' '); text += misc.quantity().toString(2) + ", "; text += misc.type() + ", "; text += misc.notes() + '\n'; } text += '\n'; // notes text += underline(tr("Notes")); // TODO: wrap long notes text += tr("Recipe Notes:\n") + recipenotes_ + "\n\n"; text += tr("Batch Notes:\n") + batchnotes_ + "\n\n"; return text; } ////////////////////////////////////////////////////////////////////////////// // exportBeerXML() // --------------- // Export recipe to BeerXML format bool Recipe::exportBeerXML(const QString &filename) { // open file QFile datafile(filename); if (!datafile.open(QFile::WriteOnly | QFile::Text)) { // error opening file qWarning() << "Error: Cannot open file" << filename; QMessageBox::warning(0, TITLE, QObject:: tr("Cannot write file %1:\n%2") .arg(filename) .arg(datafile.errorString())); datafile.close(); return false; } QApplication::setOverrideCursor(Qt::WaitCursor); // write out xml BeerXmlWriter writer(&datafile); writer.writeRecipe(this); datafile.close(); // recipe is saved, so set flags accordingly setModified(false); QApplication::restoreOverrideCursor(); return true; } ////////////////////////////////////////////////////////////////////////////// // importBeerXml() // --------------- // Import recipe from BeerXML format. Assumes beerXmlFormat() has been called bool Recipe::importBeerXml(const QString &filename) { // open file QFile datafile(filename); if (!datafile.open(QFile::ReadOnly | QFile::Text)) { // error opening file qWarning() << "Error: Cannot open" << filename; QMessageBox::warning(0, TITLE, QObject::tr("Cannot read file %1:\n%2") .arg(filename) .arg(datafile.errorString())); datafile.close(); return false; } QApplication::setOverrideCursor(Qt::WaitCursor); title_.clear(); brewer_.clear(); size_ = Data::instance()->defaultSize(); style_ = Data::instance()->defaultStyle(); grains_.clear(); hops_.clear(); miscs_.clear(); recipenotes_.clear(); batchnotes_.clear(); // parse file BeerXmlReader reader(&datafile); bool status = reader.readRecipe(this); datafile.close(); if (!status) { qWarning() << "Error: Problem reading file" << filename; qWarning() << reader.errorString(); QMessageBox::warning(0, TITLE, tr("Error reading file %1").arg(filename)); QApplication::restoreOverrideCursor(); return false; } // calculate the numbers recalc(); // just loaded recipes are not modified setModified(false); emit recipeChanged(); QApplication::restoreOverrideCursor(); return true; } ////////////////////////////////////////////////////////////////////////////// // exportPdf() // ------------ // Export recipe as pdf bool Recipe::exportPdf(TextPrinter *textprinter, const QString &filename) { if (textprinter && (!filename.isEmpty())) { QTextDocument document; document.setHtml(recipeHTML()); textprinter->exportPdf(&document, QString(), filename); return true; } return false; } qbrew-0.4.1/src/ingredientview.cpp0000644000175100017510000000562211016417220016113 0ustar daviddavid/*************************************************************************** ingredientview.cpp ------------------- Ingredient view ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include #include "ingredientview.h" /////////////////////////////////////////////////////////////////////////////// // IngredientView // /////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // IngredientView() // ------------ // Constructor IngredientView::IngredientView(QWidget *parent) : QTableView(parent), addaction_(0), removeaction_(0) { setSortingEnabled(true); // create context menu actions addaction_ = new QAction(tr("&Add Ingredient"), this); addaction_->setShortcut(QKeySequence(tr("Ctrl+Insert"))); addaction_->setStatusTip(tr("Add a new ingredient")); connect(addaction_, SIGNAL(triggered()), this, SLOT(addIngredient())); removeaction_ = new QAction(tr("&Remove Ingredient"), this); removeaction_->setShortcut(QKeySequence(tr("Ctrl+Delete"))); removeaction_->setStatusTip(tr("Remove selected ingredient")); connect(removeaction_, SIGNAL(triggered()), this, SLOT(removeIngredient())); clearaction_ = new QAction(tr("&Clear Selection"), this); clearaction_->setShortcut(QKeySequence(tr("Escape"))); clearaction_->setStatusTip(tr("Clear selection")); connect(clearaction_, SIGNAL(triggered()), this, SLOT(clearSelection())); addAction(addaction_); addAction(removeaction_); addAction(clearaction_); } IngredientView::~IngredientView() { } ////////////////////////////////////////////////////////////////////////////// // contextMenuEvent() // ------------------ // Create and show context menu void IngredientView::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); menu.addAction(addaction_); menu.addAction(removeaction_); menu.addSeparator(); menu.addAction(clearaction_); menu.exec(event->globalPos()); } ////////////////////////////////////////////////////////////////////////////// // addIngredient() // --------------- // Add a new blank ingredient void IngredientView::addIngredient() { model()->insertRows(currentIndex().row(), 1, QModelIndex()); } ////////////////////////////////////////////////////////////////////////////// // removeIngredient() // ------------------ // Remove the selected ingredient void IngredientView::removeIngredient() { // can only remove valid indexes if (currentIndex().isValid()) { model()->removeRows(currentIndex().row(), 1, QModelIndex()); clearSelection(); } } qbrew-0.4.1/src/beerxmlreader.h0000644000175100017510000000477011016417220015361 0ustar daviddavid/*************************************************************************** beerxmlreader.h ------------------- XML stream reader for BeerXML ------------------- Copyright 2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef BEERXMLREADER_H #define BEERXMLREADER_H #include #include #include "grain.h" #include "hop.h" #include "misc.h" class Recipe; class BeerXmlReader : public QXmlStreamReader { public: // constructor BeerXmlReader(QIODevice *device); // check for valid BeerXML format bool isBeerXmlFormat(); // read xml into recipe bool readRecipe(Recipe *recipe); private: // read an individual recipe Recipe readSingleRecipe(); // read grain Grain readFermentable(); // read hop Hop readHop(); // read misc Misc readMisc(); // read yeast Misc readYeast(); // read style QString readStyle(); // skip over an element void skipElement(); }; class BeerXmlWriter : public QXmlStreamWriter { public: // constructor BeerXmlWriter(QIODevice *device); // write out recipe to xml bool writeRecipe(Recipe *recipe); }; #endif // BEERXMLREADER_H qbrew-0.4.1/src/stylemodel.h0000644000175100017510000000541611016417220014717 0ustar daviddavid/*************************************************************************** stylemodel.h ------------------- Style model ------------------- Copyright 2006-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef STYLEMODEL_H #define STYLEMODEL_H #include #include #include #include "style.h" class StyleModel : public QAbstractTableModel { Q_OBJECT public: // model columns enum { NAME, OGLOW, OGHI, FGLOW, FGHI, IBULOW, IBUHI, SRMLOW, SRMHI, COUNT }; StyleModel(QObject *parent, StyleList *list); ~StyleModel(); QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); Qt::ItemFlags flags(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; bool insertRows(int row, int count, const QModelIndex & parent = QModelIndex()); bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); void flush(); signals: void modified(); private: StyleList *list_; }; #endif // STYLEMODEL_H qbrew-0.4.1/src/recipeconfig.ui0000644000175100017510000000663111016417220015361 0ustar daviddavid RecipeConfig 0 0 238 151 Recipe Configuration Recipe Defaults &Batch size batch <p>Enter the default batch size you wish to use. You may always change an individual recipe's size, but this determines the default size for new recipes.</p> true 100.000000000000000 0.250000000000000 &Recipe style stylebox <p>Select the default recipe style you wish to use. You may always change an individual recipe's style, but this determines the default style for new recipes.</p> false &Hop type hoptype <p>Select the default hops type you wish to use. You may always change an individual hop's form, but this determines the default form when adding new hops to a recipe.</p> false Qt::Vertical 220 21 batch stylebox hoptype qbrew-0.4.1/src/recipereader.cpp0000644000175100017510000002474311016417220015527 0ustar daviddavid/*************************************************************************** recipereader.cpp ------------------- XML stream reader for recipe ------------------- Copyright 2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include "recipe.h" #include "resource.h" #include "recipereader.h" using namespace Resource; // RecipeReader /////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // RecipeReader() // -------------- // Constructor RecipeReader::RecipeReader(QIODevice *device) : QXmlStreamReader(device) { } ///////////////////////////////////////////////////////////////////////////// // isRecipeFormat() // ---------------- // Is xml native Recipe format? // Defined as "recipe" doctype with generator or application as "qbrew" bool RecipeReader::isRecipeFormat() { // find root/first element do { readNext(); } while (!atEnd() && !isStartElement()); if (hasError()) return false; // check the document type if (name() != tagRecipe) return false; // check application if (attributes().value(attrApplication) != PACKAGE) { // check generator if no application if (attributes().value(attrGenerator) != PACKAGE) { return false; } } return true; } ///////////////////////////////////////////////////////////////////////////// // readRecipe() // ------------ // Read in recipe bool RecipeReader::readRecipe(Recipe *recipe) { QString buf; bool grainsflag=false, hopsflag=false, miscsflag=false; // parse file while (!atEnd()) { readNext(); if (isStartElement()) { // recipe root tag if (name() == tagRecipe) { // check application or generator if (attributes().value(attrApplication) != PACKAGE) { if (attributes().value(attrGenerator) != PACKAGE) { raiseError(QObject::tr("Not a recipe for qbrew")); return false; } } // check file version buf = attributes().value(attrVersion).toString(); if (buf < RECIPE_PREVIOUS) { // too old of a version raiseError(QObject::tr("Unsupported file version")); return false; } } // title else if (name() == tagTitle) { recipe->setTitle(readElementText()); } // brewer else if (name() == tagBrewer) { recipe->setBrewer(readElementText()); } // style // TODO: load/save entire style else if (name() == tagStyle) { recipe->setStyle(readElementText()); } // get batch settings // TODO: eliminate this tag, use quantity, efficiency else if (name() == tagBatch) { if (!attributes().value(attrQuantity).isEmpty()) { buf = attributes().value(attrQuantity).toString(); recipe->setSize(Volume(buf, Volume::gallon)); } else if (!attributes().value(attrSize).isEmpty()) { // deprecated tag buf = attributes().value(attrSize).toString(); recipe->setSize(Volume(buf, Volume::gallon)); } } // get notes else if (name() == tagNotes) { if (attributes().value(attrClass) == classRecipe) { buf = recipe->recipeNotes(); if (!buf.isEmpty()) buf += '\n'; buf += readElementText(); recipe->setRecipeNotes(buf); } else if (attributes().value(attrClass) == classBatch) { buf = recipe->batchNotes(); if (!buf.isEmpty()) buf += '\n'; buf += readElementText(); recipe->setBatchNotes(buf); } } // grains else if (name() == tagGrains) { grainsflag = true; } // grain else if (name() == tagGrain) { if (!grainsflag) qWarning("Warning: mislocated grain tag"); Grain grain; buf = attributes().value(attrQuantity).toString(); grain.setWeight(Weight(buf, Weight::pound)); buf = attributes().value(attrExtract).toString(); grain.setExtract(buf.toDouble()); buf = attributes().value(attrColor).toString(); grain.setColor(buf.toDouble()); grain.setType(attributes().value(attrType).toString()); grain.setUse(attributes().value(attrUse).toString()); grain.setName(readElementText()); recipe->addGrain(grain); } // hops else if (name() == tagHops) { hopsflag = true; } // hop else if (name() == tagHop) { // TODO: attrForm is deprecated 0.4.0 if (!hopsflag) qWarning("Warning: mislocated hop tag"); Hop hop; buf = attributes().value(attrQuantity).toString(); hop.setWeight(Weight(buf, Weight::ounce)); buf = attributes().value(attrType).toString(); if (buf.isEmpty()) buf = attributes().value(attrForm).toString(); hop.setType(buf); buf = attributes().value(attrAlpha).toString(); hop.setAlpha(buf.toDouble()); buf = attributes().value(attrTime).toString(); hop.setTime(buf.toUInt()); hop.setName(readElementText()); recipe->addHop(hop); } // miscs else if (name() == tagMiscs) { miscsflag = true; } // misc else if (name() == tagMisc) { if (!miscsflag) qWarning("Warning: mislocated misc tag"); Misc misc; buf = attributes().value(attrQuantity).toString(); misc.setQuantity(Quantity(buf, Quantity::generic)); misc.setType(attributes().value(attrType).toString()); misc.setNotes(attributes().value(attrNotes).toString()); misc.setName(readElementText()); recipe->addMisc(misc); } // unknown tag, skip else { qWarning() << "Warning: Unknown tag" << name().toString(); skipElement(); } } else if (isEndElement()) { // unset flags if (name() == tagGrains) grainsflag = false; else if (name() == tagHops) hopsflag = false; else if (name() == tagMiscs) miscsflag = false; } } return true; } // skip over xml element void RecipeReader::skipElement() { if (isStartElement()) { int level = 0; while (!atEnd()) { readNext(); if (isStartElement()) level++; if (isEndElement()) level--; if (level < 0) break; } } } // RecipeWriter /////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // RecipeWriter() // -------------- // Constructor RecipeWriter::RecipeWriter(QIODevice *device) : QXmlStreamWriter(device) { #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 0)) setAutoFormatting(true); setAutoFormattingIndent(2); #endif } ///////////////////////////////////////////////////////////////////////////// // writeRecipe() // ------------- // Write out recipe bool RecipeWriter::writeRecipe(Recipe *recipe) { writeStartDocument(); writeDTD(QString("").arg(tagRecipe)); // write root element writeStartElement(tagRecipe); writeAttribute(attrApplication, PACKAGE); writeAttribute(attrVersion, VERSION); // write recipe information writeTextElement(tagTitle, recipe->title()); writeTextElement(tagBrewer, recipe->brewer()); // TODO: save entire style writeTextElement(tagStyle, recipe->style().name()); writeEmptyElement(tagBatch); writeAttribute(attrQuantity, recipe->size().toString()); // write notes if (!recipe->recipeNotes().isEmpty()) { writeStartElement(tagNotes); writeAttribute(attrClass, classRecipe); writeCharacters(recipe->recipeNotes()); writeEndElement(); } if (!recipe->batchNotes().isEmpty()) { writeStartElement(tagNotes); writeAttribute(attrClass, classBatch); writeCharacters(recipe->batchNotes()); writeEndElement(); } // write grains writeStartElement(tagGrains); foreach(Grain grain, *recipe->grains()) { // iterate through grain list writeStartElement(tagGrain); writeAttribute(attrQuantity, grain.weight().toString()); writeAttribute(attrExtract, QString::number(grain.extract())); writeAttribute(attrColor, QString::number(grain.color())); writeAttribute(attrType, grain.type()); writeAttribute(attrUse, grain.use()); writeCharacters(grain.name()); writeEndElement(); } writeEndElement(); // tagGrains // write hops writeStartElement(tagHops); foreach(Hop hop, *recipe->hops()) { // iterate through hop list writeStartElement(tagHop); writeAttribute(attrQuantity, hop.weight().toString()); writeAttribute(attrAlpha, QString::number(hop.alpha())); writeAttribute(attrTime, QString::number(hop.time())); writeAttribute(attrType, hop.type()); writeCharacters(hop.name()); writeEndElement(); } writeEndElement(); // tagHops // write misc ingredients writeStartElement(tagMiscs); foreach(Misc misc, *recipe->miscs()) { // iterate through misc list writeStartElement(tagMisc); writeAttribute(attrQuantity, misc.quantity().toString()); writeAttribute(attrType, misc.type()); writeAttribute(attrNotes, misc.notes()); writeCharacters(misc.name()); writeEndElement(); } writeEndElement(); // tagMiscs writeEndElement(); // tagRecipe writeEndDocument(); return true; } qbrew-0.4.1/src/styledelegate.cpp0000644000175100017510000001154311016417220015722 0ustar daviddavid/*************************************************************************** styledelegate.cpp ------------------- Style delegate editor ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include #include "data.h" #include "style.h" #include "styledelegate.h" #include "stylemodel.h" StyleDelegate::StyleDelegate(QObject *parent) : QItemDelegate(parent) {} StyleDelegate::~StyleDelegate() {} QWidget *StyleDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &index) const { QLineEdit *edit; QDoubleSpinBox *dspin; QSpinBox *spin; QString suffix; // can only edit name on blank row bool blank = index.row() >= index.model()->rowCount(); // different kind of editor for each column switch (index.column()) { case StyleModel::NAME: edit = new QLineEdit(parent); edit->installEventFilter(const_cast(this)); return edit; case StyleModel::OGLOW: case StyleModel::OGHI: case StyleModel::FGLOW: case StyleModel::FGHI: if (blank) return 0; dspin = new QDoubleSpinBox(parent); dspin->setDecimals(3); dspin->setRange(0.990, 1.150); dspin->setSingleStep(0.001); dspin->setAccelerated(true); dspin->installEventFilter(const_cast(this)); return dspin; case StyleModel::IBULOW: case StyleModel::IBUHI: if (blank) return 0; spin = new QSpinBox(parent); spin->setRange(0, 120); spin->setSingleStep(1); spin->setAccelerated(true); spin->installEventFilter(const_cast(this)); return spin; case StyleModel::SRMLOW: case StyleModel::SRMHI: if (blank) return 0; spin = new QSpinBox(parent); spin->setRange(0, 50); spin->setSingleStep(1); spin->setAccelerated(true); spin->installEventFilter(const_cast(this)); return spin; default: return 0; } } void StyleDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QLineEdit *edit; QDoubleSpinBox *dspin; QSpinBox *spin; QVariant value = index.model()->data(index, Qt::EditRole); // different kind of editor for each column switch (index.column()) { case StyleModel::NAME: edit = static_cast(editor); if (!edit) return; edit->setText(value.toString()); break; case StyleModel::OGLOW: case StyleModel::OGHI: case StyleModel::FGLOW: case StyleModel::FGHI: dspin = static_cast(editor); if (!dspin) return; dspin->setValue(value.toDouble()); break; case StyleModel::IBULOW: case StyleModel::IBUHI: case StyleModel::SRMLOW: case StyleModel::SRMHI: spin = static_cast(editor); if (!spin) return; spin->setValue(value.toUInt()); break; default: QItemDelegate::setEditorData(editor, index); break; } } void StyleDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *edit; QDoubleSpinBox *dspin; QSpinBox *spin; QVariant value; // different kind of editor for each column switch (index.column()) { case StyleModel::NAME: edit = static_cast(editor); if (!edit) return; value = edit->text(); model->setData(index, value); break; case StyleModel::OGLOW: case StyleModel::OGHI: case StyleModel::FGLOW: case StyleModel::FGHI: dspin = static_cast(editor); if (!dspin) return; value = dspin->value(); model->setData(index, value); break; case StyleModel::IBULOW: case StyleModel::IBUHI: case StyleModel::SRMLOW: case StyleModel::SRMHI: spin = static_cast(editor); if (!spin) return; value = spin->value(); model->setData(index, value); break; default: QItemDelegate::setModelData(editor, model,index); break; } } void StyleDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const { editor->setGeometry(option.rect); } qbrew-0.4.1/src/hop.h0000644000175100017510000000775111016417220013330 0ustar daviddavid/*************************************************************************** hop.h ------------------- A hop class ------------------- Copyright 1999-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef HOP_H #define HOP_H #include #include #include #include "quantity.h" class Hop { public: // hop strings // TODO: make sure am comparing lowercase / handle lowercase, because old format uses it!!! static const QByteArray PELLET_STRING; static const QByteArray PLUG_STRING; static const QByteArray WHOLE_STRING; // default constructor Hop(); // full constructor Hop(const QString &name, const Weight &weight, const QString &typ, const double &alpha, const unsigned &time); // copy constructor Hop(const Hop &h); // operators Hop operator=(const Hop &h); bool operator==(const Hop &h) const; bool operator<(const Hop &h) const; // destructor ~Hop(); // get/set name const QString &name() const; void setName(const QString &name); // get/set weight const Weight &weight() const; void setWeight(const Weight &weight); // get/set alpha content void setAlpha(double alpha); double alpha() const; //get/set boil time unsigned time() const; void setTime(unsigned time); // get/set type const QString &type() const; void setType(const QString &typ); // return precalculated value for bitterness double HBU(); // return hop type static QStringList typeStringList(); private: // recalc values void recalc(); private: QString name_; Weight weight_; QString type_; double alpha_; unsigned time_; double hbu_; }; typedef QList HopList; typedef QMap HopMap; ////////////////////////////////////////////////////////////////////////////// // Inlined Operators inline bool Hop::operator<(const Hop &h) const { return (name_ < h.name_); } inline void Hop::setName(const QString &name) { name_ = name; } inline const QString &Hop::name() const { return name_; } inline void Hop::setWeight(const Weight &weight) { weight_ = weight; recalc(); } inline const Weight &Hop::weight() const { return weight_; } inline void Hop::setType(const QString &typ) { type_ = typ; } inline const QString &Hop::type() const { return type_; } inline void Hop::setAlpha(double alpha) { alpha_ = alpha; recalc(); } inline double Hop::alpha() const { return alpha_; } inline void Hop::setTime(unsigned time) { time_ = time; } inline unsigned Hop::time() const { return time_; } inline double Hop::HBU() { return hbu_; } #endif // HOP_H qbrew-0.4.1/src/grain.h0000644000175100017510000001120411016417220013626 0ustar daviddavid/*************************************************************************** grain.h ------------------- A grain class ------------------- Copyright 1999-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef GRAIN_H #define GRAIN_H #include #include #include #include "quantity.h" class Grain { public: // grain strings static const QByteArray EXTRACT_STRING; static const QByteArray MASHED_STRING; static const QByteArray STEEPED_STRING; static const QByteArray GRAIN_STRING; static const QByteArray ADJUNCT_STRING; static const QByteArray SUGAR_STRING; static const QByteArray OTHER_STRING; // default constructor Grain(); // constructor Grain(const QString &name, const Weight &quantity, const double &extract, const double &color, const QString &typ, const QString &use); // copy constructor Grain(const Grain &g); // operators Grain operator=(const Grain &g); bool operator==(const Grain &g) const; bool operator<(const Grain &g) const; // destructor ~Grain(); // get/set name const QString &name() const; void setName(const QString &name); // get/set weight const Weight &weight() const; void setWeight(const Weight &weight); // get/set extract double extract() const; void setExtract(double extract); // get/set color double color() const; void setColor(double color); // get/set type const QString &type() const; void setType(const QString &typ); // get/set usage const QString &use() const; void setUse(const QString &use); // return the yield (quantity times extract) double yield() const; // return the HCU (quantity times color) double HCU() const; // return a list of use strings static QStringList useStringList(); // return a list of type strings static QStringList typeStringList(); private: // recalc values void recalc(); private: QString name_; Weight weight_; double extract_; double color_; QString type_; QString use_; double yield_; double hcu_; }; typedef QList GrainList; typedef QMap GrainMap; ////////////////////////////////////////////////////////////////////////////// // Inlined Methods inline bool Grain::operator<(const Grain &g) const { return (name_ MainWindow 0 0 600 400 QBrew[*] 0 0 600 29 &Help &Options &Tools &File Open &Recent :/pics/icons/fileopen.png Main Toolbar Qt::Horizontal TopToolBarArea true :/pics/icons/filenew.png &New Create a new recipe Use this command to create a new recipe Ctrl+N :/pics/icons/fileopen.png &Open Open a recipe Use this command to open a recipe Ctrl+O :/pics/icons/filesave.png &Save Save the recipe Use this command to save the current recipe Ctrl+S :/pics/icons/filesaveas.png Save &as... Save the recipe Save the recipe under a new name :/pics/icons/fileprint.png &Print... Print the recipe Use this command to print the recipe Ctrl+P :/pics/icons/fileexport.png &Export... Export the recipe Export the recipe to a non-native format :/pics/icons/exit.png &Quit Quit the application Quit the application Ctrl+Q true &Statusbar Toggle the statusbar Enable or disable the Statusbar :/pics/icons/configure.png &Configure... Display the configuration dialog Display the configuration dialog :/pics/icons/contents.png &Contents Display the application handbook Display the application handbook F1 &About... Application information Display application copyright and information :/pics/icons/contexthelp.png &What's This? Context help Display context sensitive help cursor Shift+F1 &Alcohol Percentage... Alcohol percentage calculator &Hydrometer Correction... Hydrometer correction utility &Database Editor... Database editor :/pics/icons/contents.png Primer Display a brewing primer :/pics/icons/fileprint.png Print Pre&view... Preview the printed recipe Use this command to preview printing the recipe qbrew-0.4.1/src/databasetool.h0000644000175100017510000000475611016417220015206 0ustar daviddavid/*************************************************************************** databasetool.h ------------------- Database editor for QBrew ------------------- Copyright 2005-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef DATABASETOOL_H #define DATABASETOOL_H #include #include "ui_databasetool.h" #include "ui_ingredientpage.h" #include "grainmodel.h" #include "hopmodel.h" #include "miscmodel.h" #include "stylemodel.h" class QTableView; class Data; class DatabaseTool : public QMainWindow { Q_OBJECT public: // constructor DatabaseTool(QWidget* parent=0); // destructor ~DatabaseTool(); private slots: // save the database void fileSave(); // received if data has changed void dataModified(); private: Ui::DatabaseTool ui; Ui::IngredientPage grainpage; Ui::IngredientPage hoppage; Ui::IngredientPage miscpage; Ui::IngredientPage stylepage; GrainList grains_; HopList hops_; MiscList miscs_; StyleList styles_; GrainModel *grainmodel_; HopModel *hopmodel_; MiscModel *miscmodel_; StyleModel *stylemodel_; bool modified_; }; #endif // DATABASETOOL_H qbrew-0.4.1/src/hopmodel.cpp0000644000175100017510000002263511016417220014702 0ustar daviddavid/*************************************************************************** hopmodel.cpp ------------------- Hop model ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include "data.h" #include "hopmodel.h" using namespace Resource; ////////////////////////////////////////////////////////////////////////////// // HopModel() // ------------ // Constructor HopModel::HopModel(QObject *parent, HopList *list) : QAbstractTableModel(parent), list_(list) {} HopModel::~HopModel() {} ////////////////////////////////////////////////////////////////////////////// // flush() // ------- // Reset the model void HopModel::flush() { reset(); } ////////////////////////////////////////////////////////////////////////////// // data() // ------ // Return data at index QVariant HopModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= list_->count()) return QVariant(); // row is the entry in the QList const Hop &hop = list_->at(index.row()); // column is the hop "field" if (role == Qt::DisplayRole) { switch (index.column()) { case NAME: return hop.name(); case WEIGHT: return hop.weight().toString(3); case ALPHA: return QString::number(hop.alpha(), 'f', 1) + '%'; case TIME: return QString::number(hop.time()) + tr(" min", "minutes"); case TYPE: return hop.type(); default: return QVariant(); } } else if (role == Qt::EditRole) { switch (index.column()) { case NAME: return hop.name(); case WEIGHT: { // return converted quantity Weight weight = hop.weight(); weight.convert(Data::instance()->defaultHopUnit()); return weight.amount(); } case ALPHA: return hop.alpha(); case TIME: return hop.time(); case TYPE: return hop.type(); default: return QVariant(); } } else if (role == Qt::TextAlignmentRole) { switch (index.column()) { case NAME: case TYPE: return Qt::AlignLeft; case WEIGHT: case ALPHA: case TIME: default: return Qt::AlignRight; } } else { return QVariant(); } } ////////////////////////////////////////////////////////////////////////////// // setData() // --------- // Set data at index bool HopModel::setData(const QModelIndex &index, const QVariant &value, int role) { static bool deleting = false; Hop hop; QString name; int row = index.row(); int column = index.column(); if (!index.isValid()) return false; if (role != Qt::EditRole) return false; if (row >= list_->count()) return false; // grab existing hop if (row < list_->count()) hop = list_->value(row); switch (column) { case NAME: // editing name as several special cases name = value.toString(); // deleting name deletes hop if (name.isEmpty()) { if (row >= list_->count()) return false; // already blank // TODO: for some reason this gets entered recursively... if (deleting) return false; deleting = true; int status = QMessageBox::question(QApplication::activeWindow(), TITLE + tr(" - Delete?"), tr("Do you wish to remove this entry?"), QMessageBox::Yes | QMessageBox::Cancel); if (status == QMessageBox::Yes) { // remove hop beginRemoveRows(index.parent(), row, row); list_->removeAt(row); emit modified(); endRemoveRows(); deleting = false; return true; } else { // ignore deleting = false; return false; } } // no change, nothing to do if (name == list_->at(row).name()) { return false; } // changed name hop.setName(name); if (Data::instance()->hasHop(name)) { Hop newhop = Data::instance()->hop(name); // we don't override weight or time hop.setType(newhop.type()); hop.setAlpha(newhop.alpha()); } break; case WEIGHT: hop.setWeight(Weight(value.toDouble(), Data::instance()->defaultHopUnit())); break; case ALPHA: hop.setAlpha(value.toDouble()); break; case TIME: hop.setTime(value.toUInt()); break; case TYPE: hop.setType(value.toString()); break; default: return false; } list_->replace(row, hop); emit modified(); // whole row may have changed emit dataChanged(index.sibling(row, NAME), index.sibling(row, TIME)); return true; } ////////////////////////////////////////////////////////////////////////////// // insertRows() // ------------ // Insert rows into table bool HopModel::insertRows(int row, int count, const QModelIndex&) { if (count != 1) return false; // only insert one row at a time if ((row < 0) || (row >= list_->count())) row = list_->count(); Hop hop = Data::instance()->hop(tr("Generic")); beginInsertRows(QModelIndex(), row, row); list_->insert(row, hop); emit modified(); endInsertRows(); return true; } ////////////////////////////////////////////////////////////////////////////// // removeRows() // ------------ // Remove rows from table bool HopModel::removeRows(int row, int count, const QModelIndex&) { if (count != 1) return false; // only remove one row at a time if ((row < 0) || (row >= list_->count())) return false; int status = QMessageBox::question(QApplication::activeWindow(), TITLE + tr(" - Delete?"), tr("Do you wish to remove this entry?"), QMessageBox::Yes | QMessageBox::Cancel); if (status == QMessageBox::Cancel) { return false; } beginRemoveRows(QModelIndex(), row, row); list_->removeAt(row); emit modified(); endRemoveRows(); return true; } ////////////////////////////////////////////////////////////////////////////// // headerData() // ------------ // Return header information QVariant HopModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NAME: return tr("Hop"); case WEIGHT: return tr("Weight"); case ALPHA: return tr("Alpha"); case TIME: return tr("Time"); case TYPE: return tr("Type"); default: return QVariant(); } } return QVariant(); } ////////////////////////////////////////////////////////////////////////////// // flags() // ------ // Return flags at index Qt::ItemFlags HopModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } ////////////////////////////////////////////////////////////////////////////// // rowCount() // ---------- // Return number of rows of data int HopModel::rowCount(const QModelIndex &) const { return list_->count(); } ////////////////////////////////////////////////////////////////////////////// // columnCount() // ------------- // Return number of columns of data int HopModel::columnCount(const QModelIndex &) const { return COUNT; } ////////////////////////////////////////////////////////////////////////////// // sort() // ------ // Sort by column void HopModel::sort(int column, Qt::SortOrder order) { QList > sortlist; foreach(Hop hop, *list_) { QString field; switch (column) { case NAME: field = hop.name(); break; case WEIGHT: field = QString::number(hop.weight().amount()).rightJustified(8,'0'); break; case ALPHA: field = QString::number(hop.alpha(),'f',1).rightJustified(8,'0'); break; case TYPE: field = hop.type(); break; case TIME: default: field = QString::number(hop.time()).rightJustified(8,'0'); break; } sortlist.append(QPair(field, hop)); } // sort list qSort(sortlist.begin(), sortlist.end()); emit layoutAboutToBeChanged(); // create new list list_->clear(); QPair pair; foreach(pair, sortlist) { if (order == Qt::AscendingOrder) list_->append(pair.second); else list_->prepend(pair.second); } emit layoutChanged(); } qbrew-0.4.1/src/data.cpp0000644000175100017510000001651511016417220014004 0ustar daviddavid/*************************************************************************** data.cpp ------------------- Brewing data ------------------- Copyright 2001-2008 David Johnson Please see the header file for copyright and license information. ***************************************************************************/ #include #include #include #include #include #include #include "datareader.h" #include "qbrew.h" #include "recipe.h" #include "resource.h" #include "data.h" using namespace Resource; Data *Data::instance_ = 0; static QMutex instancelock; // Construction, Destruction ///////////////////////////////////////////////// // Private constructor Data::Data() : defaultsize_(Volume(5.0, Volume::gallon)), defaultstyle_(), defaulthoptype_(Hop::PELLET_STRING), defaultgrainunit_(&Weight::pound), defaulthopunit_(&Weight::ounce), defaultmiscunit_(&Quantity::generic), grainmap_(), hopmap_(), miscmap_(), stylemap_(), utable_(), steepyield_(0.5), efficiency_(0.75), tinseth_(true), morey_(true) { ; } // Private destructor Data::~Data() { ; } // Return pointer to the data Data *Data::instance() { if (!instance_) { QMutexLocker lock(&instancelock); if (!instance_) instance_ = new Data(); } return instance_; } // Initialize void Data::initialize(const ConfigState &state) { // set defaults if (state.calc.units == UNIT_METRIC) { setDefaultSize(Volume(state.recipe.batch, Volume::liter)); setDefaultGrainUnit(Weight::kilogram); setDefaultHopUnit(Weight::gram); setDefaultTempUnit(Temperature::celsius); } else if (state.calc.units == UNIT_US) { setDefaultSize(Volume(state.recipe.batch, Volume::gallon)); setDefaultGrainUnit(Weight::pound); setDefaultHopUnit(Weight::ounce); setDefaultTempUnit(Temperature::fahrenheit); } setDefaultStyle(state.recipe.style); setDefaultHopType(state.recipe.hoptype); setDefaultMiscUnit(Quantity::generic); setEfficiency(state.calc.efficiency); // load data file - look in standard locations // TODO: use QDesktopServices in next non-bugfix release (0.5.0) bool status = false; // try home directory first if (!status) status = loadData(QDIR_HOME + "/." + DATA_FILE, true); // then try system data directory if (!status) status = loadData(QBrew::instance()->dataBase() + DATA_FILE); if (!status) { qWarning() << "Warning: could not open data file"; QMessageBox::warning(0, TITLE, QObject::tr("Could not open data file")); } } ////////////////////////////////////////////////////////////////////////////// // Data Access // ////////////////////////////////////////////////////////////////////////////// void Data::setDefaultStyle(const QString &style) { if (stylemap_.contains(style)) defaultstyle_ = stylemap_.value(style); } ////////////////////////////////////////////////////////////////////////////// // style() // ------- // Return a style given its name Style Data::style(const QString &name) { if (stylemap_.contains(name)) return stylemap_.value(name); return Style(); } ////////////////////////////////////////////////////////////////////////////// // grain() // ------- // Return grain given its name Grain Data::grain(const QString &name) { Grain grain; if (grainmap_.contains(name)) grain = grainmap_.value(name); grain.setWeight(Weight(1.0, *defaultgrainunit_)); return grain; } ////////////////////////////////////////////////////////////////////////////// // hop() // ----- // Return hop given its name Hop Data::hop(const QString &name) { Hop hop; if (hopmap_.contains(name)) hop = hopmap_.value(name); hop.setWeight(Weight(1.0, *defaulthopunit_)); hop.setType(defaulthoptype_); return hop; } ////////////////////////////////////////////////////////////////////////////// // misc() // ------ // Return misc ingredient given its name Misc Data::misc(const QString &name) { Misc misc; if (miscmap_.contains(name)) misc = miscmap_.value(name); misc.setQuantity(Quantity(1.0, *defaultmiscunit_)); return misc; } ////////////////////////////////////////////////////////////////////////////// // utilization // ----------- // Look up the utilization for the given time double Data::utilization(unsigned time) { foreach(UEntry entry, utable_) { if (time >= entry.time) return (double(entry.utilization)); } return 0.0; } ////////////////////////////////////////////////////////////////////////////// // addUEntry() // ----------- // Add an entry to the utilization table [static] void Data::addUEntry(const UEntry &entry) { // keep the list sorted from highest time to lowest QList::Iterator it; if (utable_.isEmpty()) { utable_.append(entry); } else { for (it=utable_.begin(); it != utable_.end(); ++it) { if ((*it).time < entry.time) break; } utable_.insert(it, entry); } } ////////////////////////////////////////////////////////////////////////////// // Serialization // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // loadData() // ---------- // Load the data bool Data::loadData(const QString &filename, bool quiet) { // open file QFile datafile(filename); if (!datafile.open(QFile::ReadOnly | QFile::Text)) { // error opening file if (!quiet) { qWarning() << "Error: Cannot read" << filename; QMessageBox::warning(0, TITLE, QObject::tr("Cannot read file %1:\n%2") .arg(filename) .arg(datafile.errorString())); } datafile.close(); return false; } QApplication::setOverrideCursor(Qt::WaitCursor); // parse file DataReader reader(&datafile); bool status = reader.readData(this); datafile.close(); if (!status) { qWarning() << "Error: Problem reading file" << filename; qWarning() << reader.errorString(); QMessageBox::warning(0, TITLE, QObject::tr("Error reading file %1").arg(filename)); QApplication::restoreOverrideCursor(); return false; } QApplication::restoreOverrideCursor(); return true; } ////////////////////////////////////////////////////////////////////////////// // saveData() // ------------ // Save info to data file bool Data::saveData(const QString &filename) { // open file QFile datafile(filename); if (!datafile.open(QFile::WriteOnly | QFile::Text)) { // error opening file qWarning() << "Error: Cannot open file" << filename; QMessageBox::warning(0, TITLE, QObject::tr("Cannot write file %1:\n%2") .arg(filename) .arg(datafile.errorString())); datafile.close(); QApplication::restoreOverrideCursor(); return false; } // write it out DataWriter writer(&datafile); writer.writeData(this); datafile.close(); QApplication::restoreOverrideCursor(); return true; } qbrew-0.4.1/src/view.h0000644000175100017510000000513111016417220013502 0ustar daviddavid/*************************************************************************** view.h ------------------- View class for application ------------------- Copyright 2006-2008 David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef VIEW_H #define VIEW_H #include #include "ui_view.h" #include "ui_ingredientpage.h" class QTableView; class GrainModel; class HopModel; class MiscModel; class NotePage; class Recipe; class View : public QWidget { Q_OBJECT public: // constructor View(QWidget *parent, Recipe *recipe); // destructor ~View(); public slots: // set the recipe title void setTitle(const QString &title); // set the recipe style void setStyle(const QString &style); // set the brewer name void setBrewer(const QString &brewer); // set the recipe size void setSize(double size); // model has been modified void modelModified(); // flush/reset the view void flush(); // refresh just the characteristics void refresh(); private: Ui::View ui; Ui::IngredientPage grainpage; Ui::IngredientPage hoppage; Ui::IngredientPage miscpage; Recipe *recipe_; GrainModel *grainmodel_; HopModel *hopmodel_; MiscModel *miscmodel_; NotePage *notepage_; }; #endif // VIEW_H qbrew-0.4.1/src/view.cpp0000644000175100017510000002421511016417220014041 0ustar daviddavid/*************************************************************************** view.cpp ------------------- View class for application ------------------- Copyright 2006-2008 David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include "data.h" #include "recipe.h" #include "resource.h" #include "graindelegate.h" #include "grainmodel.h" #include "hopdelegate.h" #include "hopmodel.h" #include "miscdelegate.h" #include "miscmodel.h" #include "notepage.h" #include "view.h" ////////////////////////////////////////////////////////////////////////////// // Construction, Destruction // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // View() // ------ // Constructor View::View(QWidget *parent, Recipe *recipe) : QWidget(parent), recipe_(recipe), grainmodel_(0), hopmodel_(0), miscmodel_(0), notepage_(0) { ui.setupUi(this); // get current font information QFont bold(font()); bold.setBold(true); QFontMetrics fm(font()); unsigned mh = (unsigned)(fm.lineSpacing() * 1.5); unsigned mw = fm.width('M'); // additional setup ui.ogreclabel->setFont(bold); ui.ogrec->setFont(bold); ui.ibureclabel->setFont(bold); ui.iburec->setFont(bold); ui.srmreclabel->setFont(bold); ui.srmrec->setFont(bold); ui.abvlabel->setFont(bold); ui.abv->setFont(bold); ui.abwlabel->setFont(bold); ui.abw->setFont(bold); ui.fglabel->setFont(bold); ui.fg->setFont(bold); // grain page QWidget *widget = new QWidget(); grainpage.setupUi(widget); ui.ingredients->addTab(widget, tr("&Grains")); grainmodel_ = new GrainModel(this, recipe_->grains()); grainpage.view->setModel(grainmodel_); QItemDelegate *delegate = new GrainDelegate(this); grainpage.view->setItemDelegate(delegate); grainpage.view->verticalHeader()->setDefaultSectionSize(mh); grainpage.view->verticalHeader()->hide(); grainpage.view->horizontalHeader()->setClickable(true); grainpage.view->horizontalHeader()->setHighlightSections(false); grainpage.view->setColumnWidth(GrainModel::NAME, 20*mw); grainpage.view->setColumnWidth(GrainModel::WEIGHT, 8*mw); grainpage.view->setColumnWidth(GrainModel::EXTRACT, 8*mw); grainpage.view->setColumnWidth(GrainModel::COLOR, 8*mw); grainpage.view->setColumnWidth(GrainModel::TYPE, 8*mw); grainpage.view->setColumnWidth(GrainModel::USE, 8*mw); // hop page widget = new QWidget(); hoppage.setupUi(widget); ui.ingredients->addTab(widget, tr("&Hops")); hopmodel_ = new HopModel(this, recipe_->hops()); hoppage.view->setModel(hopmodel_); delegate = new HopDelegate(this); hoppage.view->setItemDelegate(delegate); hoppage.view->verticalHeader()->setDefaultSectionSize(mh); hoppage.view->verticalHeader()->hide(); hoppage.view->horizontalHeader()->setClickable(true); hoppage.view->horizontalHeader()->setHighlightSections(false); hoppage.view->setColumnWidth(HopModel::NAME, 20*mw); hoppage.view->setColumnWidth(HopModel::WEIGHT, 8*mw); hoppage.view->setColumnWidth(HopModel::ALPHA, 8*mw); hoppage.view->setColumnWidth(HopModel::TIME, 8*mw); hoppage.view->setColumnWidth(HopModel::TYPE, 8*mw); // misc page widget = new QWidget(); miscpage.setupUi(widget); ui.ingredients->addTab(widget, tr("&Miscellaneous")); miscmodel_ = new MiscModel(this, recipe_->miscs()); miscpage.view->setModel(miscmodel_); delegate = new MiscDelegate(this); miscpage.view->setItemDelegate(delegate); miscpage.view->verticalHeader()->setDefaultSectionSize(mh); miscpage.view->verticalHeader()->hide(); miscpage.view->horizontalHeader()->setClickable(true); miscpage.view->horizontalHeader()->setHighlightSections(false); miscpage.view->setColumnWidth(MiscModel::NAME, 20*mw); miscpage.view->setColumnWidth(MiscModel::QUANTITY, 8*mw); miscpage.view->setColumnWidth(MiscModel::TYPE, 8*mw); miscpage.view->horizontalHeader()->setStretchLastSection(true); // note page notepage_ = new NotePage(0, recipe_); ui.ingredients->addTab(notepage_, tr("&Notes")); // widget connections connect(grainmodel_, SIGNAL(modified()), this, SLOT(modelModified())); connect(grainpage.addbutton, SIGNAL(clicked()), grainpage.view, SLOT(addIngredient())); connect(grainpage.removebutton, SIGNAL(clicked()), grainpage.view, SLOT(removeIngredient())); connect(hopmodel_, SIGNAL(modified()), this, SLOT(modelModified())); connect(hoppage.addbutton, SIGNAL(clicked()), hoppage.view, SLOT(addIngredient())); connect(hoppage.removebutton, SIGNAL(clicked()), hoppage.view, SLOT(removeIngredient())); connect(miscmodel_, SIGNAL(modified()), this, SLOT(modelModified())); connect(miscpage.addbutton, SIGNAL(clicked()), miscpage.view, SLOT(addIngredient())); connect(miscpage.removebutton, SIGNAL(clicked()), miscpage.view, SLOT(removeIngredient())); connect(ui.titleedit, SIGNAL(textChanged(const QString &)), this, SLOT(setTitle(const QString &))); connect(ui.stylecombo, SIGNAL(activated(const QString &)), this, SLOT(setStyle(const QString &))); connect(ui.breweredit, SIGNAL(textChanged(const QString &)), this, SLOT(setBrewer(const QString &))); connect(ui.sizespin, SIGNAL(valueChanged(double)), this, SLOT(setSize(double))); connect(recipe_, SIGNAL(recipeChanged()), this, SLOT(flush())); connect(recipe_, SIGNAL(recipeModified()), this, SLOT(refresh())); // start with new view flush(); } View::~View() { ; } ////////////////////////////////////////////////////////////////////////////// // Slot Implementations // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // setTitle() // ---------- // set the recipe title void View::setTitle(const QString &title) { recipe_->setTitle(title); } ////////////////////////////////////////////////////////////////////////////// // setStyle() // ---------- // set the recipe style void View::setStyle(const QString &style) { recipe_->setStyle(style); } ////////////////////////////////////////////////////////////////////////////// // Brewer() // -------- // set the recipeCombo brewer void View::setBrewer(const QString &brewer) { recipe_->setBrewer(brewer); } ////////////////////////////////////////////////////////////////////////////// // setSize() // --------- // set the recipe size void View::setSize(double size) { recipe_->setSize(Volume(size, recipe_->size().unit())); } /////////////////////////////////////////////////////////////////////////////// // Miscellaneous // /////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // modelModified() // --------------- // One of the models has been modified void View::modelModified() { recipe_->recalc(); recipe_->setModified(true); } ////////////////////////////////////////////////////////////////////////////// // flush() // ------- // flush/reset the entire view void View::flush() { // save the modified flag to prevent side effects of setting widgets bool oldmod = recipe_->modified(); recipe_->blockSignals(true); // update recipe widgets ui.titleedit->setText(recipe_->title()); ui.breweredit->setText(recipe_->brewer()); ui.sizespin->setValue(recipe_->size().amount()); ui.sizespin->setSuffix(" " + recipe_->size().unit().name()); // set style combo to style ui.stylecombo->clear(); ui.stylecombo->addItems(Data::instance()->stylesList()); int index = ui.stylecombo->findText(recipe_->style().name(), Qt::MatchExactly); if (index >= 0) { ui.stylecombo->setCurrentIndex(index); } else { ui.stylecombo->addItem(recipe_->style().name()); } // restore modified flag recipe_->setModified(oldmod); recipe_->blockSignals(false); // reset ingredient models grainmodel_->flush(); hopmodel_->flush(); miscmodel_->flush(); notepage_->refresh(); grainpage.view->sortByColumn(0, Qt::AscendingOrder); hoppage.view->sortByColumn(0, Qt::AscendingOrder); miscpage.view->sortByColumn(0, Qt::AscendingOrder); // update style widgets refresh(); } ////////////////////////////////////////////////////////////////////////////// // refresh() // --------- // refresh just the characteristics void View::refresh() { const Style &style = recipe_->style(); QLocale locale = QLocale::system(); // beer style has changed, so update labels in stylelayout_ ui.stylebox->setTitle(tr("%1 characteristics", "Beer style characteristics, e.g. Generic ale") .arg(ui.stylecombo->currentText())); ui.ogmin->setText(locale.toString(style.OGLow(), 'f', 3)); ui.ogmax->setText(locale.toString(style.OGHi(), 'f', 3)); ui.ogrec->setText(locale.toString(recipe_->OG(), 'f', 3)); ui.ibumin->setText(locale.toString(style.IBULow())); ui.ibumax->setText(locale.toString(style.IBUHi())); ui.iburec->setText(locale.toString(recipe_->IBU())); ui.srmmin->setText(locale.toString(style.SRMLow()) + Resource::DEGREE); ui.srmmax->setText(locale.toString(style.SRMHi()) + Resource::DEGREE); ui.srmrec->setText(locale.toString(recipe_->SRM()) + Resource::DEGREE); ui.fg->setText(locale.toString(recipe_->FGEstimate(), 'f', 3)); ui.abv->setText(QString(tr("%1%")) .arg(locale.toString(recipe_->ABV() * 100.0, 'f', 1))); ui.abw->setText(QString(tr("%1%")) .arg(locale.toString(recipe_->ABW() * 100.0, 'f', 1))); } qbrew-0.4.1/src/helpviewer.h0000644000175100017510000000423311016417220014704 0ustar daviddavid/*************************************************************************** helpviewer.h ------------------- General purpose help file viewer ------------------- Copyright 2007-2008 David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ #ifndef HELPVIEWER_H #define HELPVIEWER_H #include #include "ui_helpviewer.h" class QPrinter; class QTextBrowser; class HelpViewer : public QMainWindow { Q_OBJECT public: // constructor HelpViewer(const QString& home, QWidget* parent=0); // destructor ~HelpViewer(); private slots: // if text has changed void textChanged(); // print contents of browser void print(); // hypertext link clicked void anchorClicked(const QUrl &link); private: Ui::HelpViewer ui; QTextBrowser* browser_; QPrinter* printer_; }; #endif // HELPVIEWER_H qbrew-0.4.1/src/beerxmlreader.cpp0000644000175100017510000005201211016417220015704 0ustar daviddavid/*************************************************************************** beerxmlreader.cpp ------------------- XML stream reader for BeerXML ------------------- Copyright 2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include "data.h" #include "recipe.h" #include "resource.h" #include "beerxmlreader.h" using namespace Resource; // BeerXmlReader ///////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // BeerXmlReader() // --------------- // Constructor BeerXmlReader::BeerXmlReader(QIODevice *device) : QXmlStreamReader(device) { } ///////////////////////////////////////////////////////////////////////////// // isBeerXmlFormat() // ----------------- // Is xml BeerXML format? // Defined as XML with "" and "", VERSION 1 // Note that BeerXML 1.0 is poorly designed format bool BeerXmlReader::isBeerXmlFormat() { // find root/first element do { readNext(); } while (!atEnd() && !isStartElement()); if (hasError()) return false; // check the document type if (name() != tagRECIPES) return false; // check for RECIPE while (!atEnd()) { readNext(); if (isStartElement()) { if (name() == tagRECIPE) { // check VERSION while (!atEnd()) { readNext(); if (isStartElement()) { if (name() == tagVERSION) { if (readElementText() == beerXMLVersion) return true; } } } } } } return false; } ///////////////////////////////////////////////////////////////////////////// // readRecipe() // ------------ // Read in recipe bool BeerXmlReader::readRecipe(Recipe *recipe) { QString buf; // BeerXML may have more than one recipe, // so we read each one into a different recipe object QMap recipes; while (!atEnd()) { readNext(); if (isStartElement()) { if (name() == tagRECIPES) { while (!atEnd()) { readNext(); if (isStartElement()) { if (name() == tagRECIPE) { Recipe r = readSingleRecipe(); if (!r.title().isEmpty()) { recipes[r.title()] = r; } } else { skipElement(); } } } } else { qWarning() << "Warning: Unknown tag" << name().toString(); skipElement(); } } } if (hasError()) return false; if (recipes.count() == 0) { raiseError("No recipes found"); return false; } // select just one recipe if (recipes.count() > 1) { bool ok; QString name = QInputDialog::getItem(0, TITLE, QObject::tr("Multiple recipes found. Please select one:"), recipes.keys(), 0, false, &ok); if (ok) { // copy over recipe *recipe = recipes[name]; } } else { // only one recipe *recipe = recipes.begin().value(); } return true; } ////////////////////////////////////////////////////////////////////////////// // readSingleRecipe() // ------------------ // Read an individual recipe from xml Recipe BeerXmlReader::readSingleRecipe() { Recipe recipe(0); QString buf; bool mashed = false; bool grainsflag=false, hopsflag=false, miscsflag=false, yeastsflag=false; while (!atEnd()) { readNext(); if (isStartElement()) { // version if (name() == tagVERSION) { if (readElementText() != beerXMLVersion) { qWarning() << "Wrong BeerXML version"; return Recipe(); } } // name else if (name() == tagNAME) { recipe.setTitle(readElementText()); } // brewer else if (name() == tagBREWER) { recipe.setBrewer(readElementText()); } // style else if (name() == tagSTYLE) { recipe.setStyle(readStyle()); } // mashed else if (name() == tagTYPE) { mashed = (readElementText() != Grain::EXTRACT_STRING); } // batch size else if (name() == tagBATCHSIZE) { buf = readElementText(); recipe.setSize(Volume(buf, Volume::liter)); } // notes else if (name() == tagNOTES) { recipe.setRecipeNotes(readElementText()); } else if (name() == tagTASTENOTES) { recipe.setBatchNotes(readElementText()); } // fermentables else if (name() == tagFERMENTABLES) { grainsflag = true; } // fermentable else if (name() == tagFERMENTABLE) { if (!grainsflag) qWarning("Warning: mislocated fermentable tag"); recipe.addGrain(readFermentable()); } // hops else if (name() == tagHOPS) { hopsflag = true; } // hop else if (name() == tagHOP) { if (!hopsflag) qWarning("Warning: mislocated hop tag"); recipe.addHop(readHop()); } // miscs else if (name() == tagMISCS) { miscsflag = true; } // misc else if (name() == tagMISC) { if (!miscsflag) qWarning("Warning: mislocated misc tag"); recipe.addMisc(readMisc()); } // yeasts else if (name() == tagYEASTS) { yeastsflag = true; } // yeast else if (name() == tagYEAST) { if (!yeastsflag) qWarning("Warning: mislocated yeast tag"); recipe.addMisc(readYeast()); } else if (name() == tagEFFICIENCY) { // unused, ignore tag... skipElement(); } else if (name() == tagWATERS) { // unused, ignore tag... skipElement(); } // unknown tag, skip else { skipElement(); } } else if (isEndElement()) { if (name() == tagRECIPE) break; // unset flags else if (name() == tagFERMENTABLES) grainsflag = false; else if (name() == tagHOPS) hopsflag = false; else if (name() == tagMISCS) miscsflag = false; else if (name() == tagYEASTS) yeastsflag = false; } } if (hasError()) { raiseError("Problem reading recipe"); return false; } // set grain use according to recipe type for (int n=0; ncount(); n++) { if (mashed) (*recipe.grains())[n].setUse(Grain::MASHED_STRING); else (*recipe.grains())[n].setUse(Grain::STEEPED_STRING); } return recipe; } // read grain Grain BeerXmlReader::readFermentable() { QString buf; Grain grain; while (!atEnd()) { readNext(); if (isStartElement()) { // version if (name() == tagVERSION) { if (readElementText() != beerXMLVersion) { qWarning() << "Error: Wrong BeerXML version"; return Grain(); } } // name else if (name() == tagNAME) { grain.setName(readElementText()); } // amount else if (name() == tagAMOUNT) { buf = readElementText(); grain.setWeight(Weight(buf.toDouble(), Weight::kilogram)); } // yield else if (name() == tagYIELD) { buf = readElementText(); grain.setExtract(Recipe::yieldToExtract(buf.toDouble() / 100.0)); } // color else if (name() == tagCOLOR) { buf = readElementText(); grain.setColor(buf.toDouble()); } // type (and guess at use) else if (name() == tagTYPE) { buf = readElementText(); grain.setType(Grain::OTHER_STRING); grain.setUse(Grain::OTHER_STRING); if (buf == "Grain") { grain.setType(Grain::GRAIN_STRING); } if (buf == "Sugar") { grain.setType(Grain::SUGAR_STRING); grain.setUse(Grain::OTHER_STRING); } if ((buf == "Extract") || (buf == "Dry Extract")) { grain.setType(Grain::EXTRACT_STRING); grain.setUse(Grain::EXTRACT_STRING); } if (buf== "Adjunct") { grain.setType(Grain::ADJUNCT_STRING); grain.setUse(Grain::MASHED_STRING); } } } else if (isEndElement()) { if (name() == tagFERMENTABLE) break; } } return grain; } // read hop Hop BeerXmlReader::readHop() { QString buf; Hop hop; // TODO: add USE to future hop notes while (!atEnd()) { readNext(); if (isStartElement()) { // version if (name() == tagVERSION) { if (readElementText() != beerXMLVersion) { qWarning() << "Error: Wrong BeerXML version"; return hop; } } // name if (name() == tagNAME) { hop.setName(readElementText()); } // amount else if (name() == tagAMOUNT) { buf = readElementText(); hop.setWeight(Weight(buf.toDouble(), Weight::kilogram)); } // alpha else if (name() == tagALPHA) { buf = readElementText(); hop.setAlpha(buf.toDouble()); } // time else if (name() == tagTIME) { buf = readElementText(); hop.setTime((int)buf.toDouble()); } // form else if (name() == tagFORM) { buf = readElementText(); if (buf == "Pellet") { hop.setType(Hop::PELLET_STRING); } else if (buf == "Plug") { hop.setType(Hop::PLUG_STRING); } else { hop.setType(Hop::WHOLE_STRING); } } } else if (isEndElement()) { if (name() == tagHOP) break; } } return hop; } // read misc ingredient Misc BeerXmlReader::readMisc() { QString buf; Misc misc; double temp_amount = 0; bool temp_aiw = false; // TODO: put TYPE and USE in notes while (!atEnd()) { readNext(); if (isStartElement()) { // version if (name() == tagVERSION) { if (readElementText() != beerXMLVersion) { qWarning() << "Error: Wrong BeerXML version"; return misc; } } // name if (name() == tagNAME) { misc.setName(readElementText()); } // amount else if (name() == tagAMOUNT) { buf = readElementText(); temp_amount = buf.toDouble(); } // amount is weight else if (name() == tagAMOUNTISWEIGHT) { buf = readElementText(); temp_aiw = (buf.toLower() == "true"); } // notes if (name() == tagNOTES) { misc.setNotes(readElementText()); } // type else if (name() == tagTYPE) { buf = readElementText(); misc.setType(Misc::OTHER_STRING); if (buf == "Spice") misc.setType(Misc::SPICE_STRING); if (buf == "Fining") misc.setType(Misc::FINING_STRING); if (buf == "Herb") misc.setType(Misc::HERB_STRING); if (buf == "Flavor") misc.setType(Misc::FLAVOR_STRING); if (buf == "Water Agent") misc.setType(Misc::ADDITIVE_STRING); } } else if (isEndElement()) { if (name() == tagMISC) break; } } if (temp_aiw) { misc.setQuantity(Weight(temp_amount, Weight::kilogram)); } else { misc.setQuantity(Volume(temp_amount, Volume::liter)); } return misc; } // read yeast Misc BeerXmlReader::readYeast() { QString buf, notes; Misc misc; double temp_amount = 0; bool temp_aiw = false; misc.setType(Misc::YEAST_STRING); while (!atEnd()) { readNext(); if (isStartElement()) { // version if (name() == tagVERSION) { if (readElementText() != beerXMLVersion) { qWarning() << "Error: Wrong BeerXML version"; return misc; } } // name if (name() == tagNAME) { misc.setName(readElementText()); } // amount else if (name() == tagAMOUNT) { buf = readElementText(); temp_amount = buf.toDouble(); } // amount is weight else if (name() == tagAMOUNTISWEIGHT) { buf = readElementText(); temp_aiw = (buf.toLower() == "true"); } // notes if (name() == tagNOTES) { if (!notes.isEmpty()) notes += ' '; notes += readElementText(); } // form else if (name() == tagFORM) { if (!notes.isEmpty()) notes += ' '; notes += readElementText() + " yeast."; } } else if (isEndElement()) { if (name() == tagYEAST) break; } } misc.setNotes(notes); if (temp_aiw) { misc.setQuantity(Weight(temp_amount, Weight::kilogram)); } else { misc.setQuantity(Volume(temp_amount, Volume::liter)); } return misc; } // read style from xml QString BeerXmlReader::readStyle() { // the only thing I care about is name QString stylename; while (!atEnd()) { readNext(); if (isStartElement()) { if (name() == tagNAME) { stylename = readElementText(); } } else if (isEndElement()) { if (name() == tagSTYLE) break; } } return stylename; } // skip over xml element void BeerXmlReader::skipElement() { if (isStartElement()) { int level = 0; while (!atEnd()) { readNext(); if (isStartElement()) level++; if (isEndElement()) level--; if (level < 0) break; } } } // BeerXmlWriter ///////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // RecipeWriter() // -------------- // Constructor BeerXmlWriter::BeerXmlWriter(QIODevice *device) : QXmlStreamWriter(device) { #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 0)) setAutoFormatting(true); setAutoFormattingIndent(2); #endif } ////////////////////////////////////////////////////////////////////////////// // writeRecipe() // ------------- // Write out recipe bool BeerXmlWriter::writeRecipe(Recipe *recipe) { QString buf; writeStartDocument(); // BeerXML 1 doesn't have a doctype writeComment(QString("BeerXML generated by %1 %2") .arg(PACKAGE).arg(VERSION)); writeStartElement(tagRECIPES); // write recipe information writeStartElement(tagRECIPE); writeTextElement(tagVERSION, beerXMLVersion); writeTextElement(tagNAME, recipe->title()); writeTextElement(tagTYPE, recipe->method()); writeTextElement(tagBREWER, recipe->brewer()); double size = recipe->size().amount(Volume::liter); writeTextElement(tagBATCHSIZE, QString::number(size,'f',8)); writeTextElement(tagEFFICIENCY, QString::number(Data::instance()->efficiency()*100.0,'f',1)); // write style // TODO: unfinished... // category // category number // style letter // style guide // type // og writeStartElement(tagSTYLE); writeTextElement(tagVERSION, beerXMLVersion); writeTextElement(tagNAME, recipe->style().name()); writeTextElement(tagOGMIN, QString::number(recipe->style().OGLow(),'f',4)); writeTextElement(tagOGMAX, QString::number(recipe->style().OGHi(),'f',4)); writeTextElement(tagFGMIN, QString::number(recipe->style().FGLow(),'f',4)); writeTextElement(tagFGMAX, QString::number(recipe->style().FGHi(),'f',4)); writeTextElement(tagIBUMIN, QString::number(recipe->style().IBULow(),'f',2)); writeTextElement(tagIBUMAX, QString::number(recipe->style().IBUHi(),'f',2)); writeTextElement(tagCOLORMIN, QString::number(recipe->style().SRMLow(),'f',2)); writeTextElement(tagCOLORMAX, QString::number(recipe->style().SRMHi(),'f',2)); writeEndElement(); // tagSTYLE // fermentables list writeStartElement(tagFERMENTABLES); foreach (Grain grain, *recipe->grains()) { writeStartElement(tagFERMENTABLE); writeTextElement(tagVERSION, beerXMLVersion); writeTextElement(tagNAME, grain.name()); size = grain.weight().amount(Weight::kilogram); writeTextElement(tagAMOUNT, QString::number(size,'f',8)); writeTextElement(tagCOLOR, QString::number(grain.color(),'f',2)); double yield = recipe->extractToYield(grain.extract()) * 100.0; writeTextElement(tagYIELD, QString::number(yield,'f',2)); writeTextElement(tagTYPE, grain.type()); writeEndElement(); // tagFERMENTABLE } writeEndElement(); // tagFERMENTABLES // hop list writeStartElement(tagHOPS); foreach (Hop hop, *recipe->hops()) { writeStartElement(tagHOP); writeTextElement(tagVERSION, beerXMLVersion); writeTextElement(tagNAME, hop.name()); size = hop.weight().amount(Weight::kilogram); writeTextElement(tagAMOUNT, QString::number(size,'f',8)); writeTextElement(tagALPHA, QString::number(hop.alpha(),'f',2)); // TODO: note that I'm using "Boil" for all hops... writeTextElement(tagUSE, "Boil"); writeTextElement(tagTIME, QString::number(hop.time())); buf = "Leaf"; if (hop.type() == Hop::PELLET_STRING) buf = "Pellet"; if (hop.type() == Hop::PLUG_STRING) buf = "Plug"; writeTextElement(tagFORM, buf); writeEndElement(); // tagHOP } writeEndElement(); // tagHOPS // yeast list writeStartElement(tagYEASTS); foreach (Misc misc, *recipe->miscs()) { // iterate through list looking for yeasts if (misc.type() == Misc::YEAST_STRING) { writeStartElement(tagYEAST); writeTextElement(tagVERSION, beerXMLVersion); writeTextElement(tagNAME, misc.name()); writeTextElement(tagAMOUNT, "0.00"); // TODO: fixup for miscs writeTextElement(tagTYPE, Misc::YEAST_STRING); // TODO: form??? writeTextElement(tagNOTES, misc.notes()); writeEndElement(); // tagYEAST } } writeEndElement(); // tagYEASTS // misc list writeStartElement(tagMISCS); foreach (Misc misc, *recipe->miscs()) { // iterate through list looking for yeasts if (misc.type() != Misc::YEAST_STRING) { writeStartElement(tagMISC); writeTextElement(tagVERSION, beerXMLVersion); writeTextElement(tagNAME, misc.name()); writeTextElement(tagAMOUNT, "0.00"); // TODO: fixup for miscs writeTextElement(tagTYPE, misc.type()); writeTextElement(tagNOTES, misc.notes()); // use ??? // time ??? writeEndElement(); // tagMISC } } writeEndElement(); // tagMISCS // waters // NOTE: not currently supporting water writeStartElement(tagWATERS); writeEndElement(); // mash // NOTE: not currently supporting mash // notes if (!(recipe->recipeNotes().isEmpty() && recipe->batchNotes().isEmpty())) { buf = recipe->recipeNotes(); if (!recipe->batchNotes().isEmpty()) { if (!buf.isEmpty()) buf += "\n\n"; buf += recipe->batchNotes(); } writeTextElement(tagNOTES, buf); } writeEndElement(); // tagRECIPE writeEndElement(); // tagRECIPES return true; } qbrew-0.4.1/src/stylemodel.cpp0000644000175100017510000002415511016417220015253 0ustar daviddavid/*************************************************************************** stylemodel.cpp ------------------- Style model ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include "data.h" #include "stylemodel.h" using namespace Resource; ////////////////////////////////////////////////////////////////////////////// // StyleModel() // ------------ // Constructor StyleModel::StyleModel(QObject *parent, StyleList *list) : QAbstractTableModel(parent), list_(list) {} StyleModel::~StyleModel(){} ////////////////////////////////////////////////////////////////////////////// // flush() // ------- // Reset the model void StyleModel::flush() { reset(); } ////////////////////////////////////////////////////////////////////////////// // data() // ------ // Return data at index QVariant StyleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= list_->count()) return QVariant(); // row is the entry in the QList const Style &style = list_->at(index.row()); // column is the style "field" if (role == Qt::DisplayRole) { switch (index.column()) { case NAME: return style.name(); case OGLOW: return QString::number(style.oglow_, 'f', 3); case OGHI: return QString::number(style.oghi_, 'f', 3); case FGLOW: return QString::number(style.fglow_, 'f', 3); case FGHI: return QString::number(style.fghi_, 'f', 3); case IBULOW: return QString::number(style.ibulow_); case IBUHI: return QString::number(style.ibuhi_); case SRMLOW: return QString::number(style.srmlow_); case SRMHI: return QString::number(style.srmhi_); default: return QVariant(); } } else if (role == Qt::EditRole) { switch (index.column()) { case NAME: return style.name(); case OGLOW: return style.oglow_; case OGHI: return style.oghi_; case FGLOW: return style.fglow_; case FGHI: return style.fghi_; case IBULOW: return style.ibulow_; case IBUHI: return style.ibuhi_; case SRMLOW: return style.srmlow_; case SRMHI: return style.srmhi_; default: return QVariant(); } } else if (role == Qt::TextAlignmentRole) { switch (index.column()) { case NAME: return Qt::AlignLeft; case OGLOW: case OGHI: case FGLOW: case FGHI: case IBULOW: case IBUHI: case SRMLOW: case SRMHI: default: return Qt::AlignRight; } } else { return QVariant(); } } ////////////////////////////////////////////////////////////////////////////// // setData() // --------- // Set data at index bool StyleModel::setData(const QModelIndex &index, const QVariant &value, int role) { static bool deleting = false; Style style; QString name; int row = index.row(); int column = index.column(); if (!index.isValid()) return false; if (role != Qt::EditRole) return false; if ((row >= list_->count()) && (column != NAME)) return false; // grab existing style if (row < list_->count()) style = list_->value(row); switch (column) { case NAME: // editing name as several special cases name = value.toString(); // deleting name deletes style if (name.isEmpty()) { if (row >= list_->count()) return false; // empty // TODO: for some reason this gets entered recursively... if (deleting) return false; deleting = true; // remove style beginRemoveRows(index.parent(), row, row); list_->removeAt(row); emit modified(); endRemoveRows(); deleting = false; return true; } // changed name style.setName(name); break; case OGLOW: style.oglow_ = value.toDouble(); break; case OGHI: style.oghi_ = value.toDouble(); break; case FGLOW: style.fglow_ = value.toDouble(); break; case FGHI: style.fghi_ = value.toDouble(); break; case IBULOW: style.ibulow_ = value.toUInt(); break; case IBUHI: style.ibuhi_ = value.toUInt(); break; case SRMLOW: style.srmlow_ = value.toUInt(); break; case SRMHI: style.srmhi_ = value.toUInt(); break; default: return false; } list_->replace(row, style); emit modified(); // whole row may have changed emit dataChanged(index.sibling(row, NAME), index.sibling(row, SRMHI)); return true; } ////////////////////////////////////////////////////////////////////////////// // insertRows() // ------------ // Insert rows into table bool StyleModel::insertRows(int row, int count, const QModelIndex&) { if (count != 1) return false; // only insert one row at a time if ((row < 0) || (row >= list_->count())) row = list_->count(); Style style = Data::instance()->style(tr("Generic")); beginInsertRows(QModelIndex(), row, row); list_->insert(row, style); emit modified(); endInsertRows(); return true; } ////////////////////////////////////////////////////////////////////////////// // removeRows() // ------------ // Remove rows from table bool StyleModel::removeRows(int row, int count, const QModelIndex&) { if (count != 1) return false; // only remove one row at a time if ((row < 0) || (row >= list_->count())) return false; int status = QMessageBox::question(QApplication::activeWindow(), TITLE + tr(" - Delete?"), tr("Do you wish to remove this entry?"), QMessageBox::Yes | QMessageBox::Cancel); if (status == QMessageBox::Cancel) { return false; } beginRemoveRows(QModelIndex(), row, row); list_->removeAt(row); emit modified(); endRemoveRows(); return true; } ////////////////////////////////////////////////////////////////////////////// // headerData() // ------------ // Return header information QVariant StyleModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NAME: return tr("Style"); case OGLOW: return tr("Min. OG"); case OGHI: return tr("Max. OG"); case FGLOW: return tr("Min. FG"); case FGHI: return tr("Max. FG"); case IBULOW: return tr("Min. IBU"); case IBUHI: return tr("Max. IBU"); case SRMLOW: return tr("Min. SRM"); case SRMHI: return tr("Max. SRM"); default: return QVariant(); } } return QVariant(); } ////////////////////////////////////////////////////////////////////////////// // flags() // ------ // Return flags at index Qt::ItemFlags StyleModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } ////////////////////////////////////////////////////////////////////////////// // rowCount() // ---------- // Return number of rows of data int StyleModel::rowCount(const QModelIndex &) const { return list_->count(); } ////////////////////////////////////////////////////////////////////////////// // columnCount() // ------------- // Return number of columns of data int StyleModel::columnCount(const QModelIndex &) const { return COUNT; } ////////////////////////////////////////////////////////////////////////////// // sort() // ------ // Sort by column void StyleModel::sort(int column, Qt::SortOrder order) { QList > sortlist; foreach(Style style, *list_) { QString field; switch (column) { case NAME: field = style.name(); break; case OGLOW: field = QString::number(style.oglow_).rightJustified(6,'0'); break; case OGHI: field = QString::number(style.oghi_).rightJustified(6,'0'); break; case FGLOW: field = QString::number(style.fglow_).rightJustified(6,'0'); break; case FGHI: field = QString::number(style.fghi_).rightJustified(6,'0'); break; case IBULOW: field = QString::number(style.ibulow_).rightJustified(6,'0'); break; case IBUHI: field = QString::number(style.ibuhi_).rightJustified(6,'0'); break; case SRMLOW: field = QString::number(style.srmlow_).rightJustified(6,'0'); break; case SRMHI: field = QString::number(style.srmhi_).rightJustified(6,'0'); break; default: field = QString(); break; } sortlist.append(QPair(field, style)); } // sort list qSort(sortlist.begin(), sortlist.end()); // create new list list_->clear(); QPair pair; foreach(pair, sortlist) { if (order == Qt::AscendingOrder) list_->append(pair.second); else list_->prepend(pair.second); } emit layoutChanged(); } qbrew-0.4.1/src/main.cpp0000644000175100017510000001230611016417220014011 0ustar daviddavid/*************************************************************************** main.cpp ------------------- A brewing recipe calculator ------------------- Copyright 1999-2008 David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****************************************************************************/ #include #include #include #include #include #include #include #include #if defined(Q_WS_MACX) extern void qt_mac_set_menubar_icons(bool enable); #endif #include "qbrew.h" #include "resource.h" using namespace std; using namespace Resource; ////////////////////////////////////////////////////////////////////////////// // doversion() // ----------- // Print out copyright and version info to stdout void doversion(QTextStream &stream) { stream << PACKAGE << ' ' << VERSION << '\n'; stream << QObject::tr(DESCRIPTION) << '\n'; stream << QObject::tr(COPYRIGHT); stream << ' ' << AUTHOR; stream << " <" << AUTHOR_EMAIL << ">"; stream << endl; } ////////////////////////////////////////////////////////////////////////////// // dohelp() // -------- // Print out help info to stdout void dohelp(QTextStream &stream) { stream << QObject::tr("Usage: %1 [options] [file]\n").arg(PACKAGE); stream << QObject::tr(DESCRIPTION) << "\n\n"; // arguments stream << QObject::tr("Arguments\n"); stream << QObject::tr(" file File to open") << "\n\n"; // general options stream << QObject::tr("Options\n"); stream << QObject::tr(" --help Print the command line options.\n"); stream << QObject::tr(" --version Print the application version.\n"); stream << endl; } ////////////////////////////////////////////////////////////////////////////// // doargs() // -------- // Process the arguments that QApplication doesn't take care of QString doargs(QStringList arguments, QTextStream &stream) { QString arg; for (int n=1; ndataBase() + "translations"; QTranslator translator; if (translator.load("qbrew_" + QLocale::system().name(), transdir)) { app.installTranslator(&translator); } // load qt translations QString qttransdir = QLibraryInfo::location(QLibraryInfo::TranslationsPath); QTranslator qttranslator; if (qttranslator.load("qt_" + QLocale::system().name(), qttransdir)) { app.installTranslator(&qttranslator); } // check for additional command line arguments QTextStream stream(stdout); QString filename = doargs(app.arguments(), stream); QBrew* mainwindow = QBrew::instance(); mainwindow->initialize(filename); mainwindow->show(); app.installEventFilter(mainwindow); // for handling file open events return app.exec(); } qbrew-0.4.1/src/alcoholtool.h0000644000175100017510000000366211016417220015056 0ustar daviddavid/*************************************************************************** aloholtool.h ------------------- An Alcohol Percentage Calculator utility for QBrew ------------------- Copyright 2004-2008, David Johnson Based on code Copyright 2004, Michal Palczewski All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #include #include "ui_alcoholtool.h" class AlcoholTool : public QDialog { Q_OBJECT public: // constructor AlcoholTool(QWidget* parent=0); public slots: // for calculating the actual SG void recalc(); private: Ui::AlcoholTool ui; }; qbrew-0.4.1/src/hopdelegate.h0000644000175100017510000000472211016417220015016 0ustar daviddavid/*************************************************************************** hopdelegate.h ------------------- Hop delegate editor ------------------- Copyright 2006-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef HOPDELEGATE_H #define HOPDELEGATE_H #include // TODO: inherit from QStyleItemDelegate when available in 4.4 class HopDelegate : public QItemDelegate { Q_OBJECT public: HopDelegate(QObject *parent = 0); ~HopDelegate(); // create editor widget QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; // set the contents of the editor void setEditorData(QWidget *editor, const QModelIndex &index) const; // set the contents of the model void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; // manage the editor's geometry void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; #endif // HOPDELEGATE_H qbrew-0.4.1/src/resource.h0000644000175100017510000002472211016417220014366 0ustar daviddavid/*************************************************************************** resource.h ------------------- Global application resources. Constants, enumerations and strings. ------------------- Copyright 2006-2008 David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef RESOURCE_H #define RESOURCE_H #include #include #define PACKAGE "qbrew" #define VERSION "0.4.1" // some stuff to differentiate between platforms #if defined(Q_WS_X11) #define QDIR_HOME QDir::homePath() #elif defined(Q_WS_MACX) #define QDIR_HOME QDir::homePath() #else #define QDIR_HOME QDir::currentPath() #endif namespace Resource { /////////////////////////////////////////////////////////////////// // general application values const QByteArray TITLE = "QBrew"; const QByteArray DESCRIPTION = QT_TRANSLATE_NOOP("description", "A Homebrewer's Recipe Calculator"); const QByteArray COPYRIGHT = QT_TRANSLATE_NOOP("copyright", "Copyright 1999-2008"); const QByteArray AUTHOR = "David Johnson"; const QByteArray AUTHOR_EMAIL = "david@usermode.org"; const QByteArray CONTRIBUTORS = "Lee Henderson, " "Rob Hudson, " "Abe Kabakoff, " "Stephen Lowrie, " "Michal Palczewski, " "Kevin Pullin, " "Tobias Toedter"; const QByteArray READY = QT_TRANSLATE_NOOP("message", "Ready"); const QChar DEGREE = QChar(0xB0); // ISO 8859-1 /////////////////////////////////////////////////////////////////// // file locations and stuff const QByteArray FILE_EXT = PACKAGE; const QByteArray FILE_FILTER = QT_TRANSLATE_NOOP("file filter", "QBrew files (*.qbrew)"); const QByteArray TEXT_FILTER = QT_TRANSLATE_NOOP("file filter", "Text files (*.txt)"); const QByteArray HTML_FILTER = QT_TRANSLATE_NOOP("file filter", "HTML files (*.html *.htm)"); const QByteArray BEERXML_FILTER = QT_TRANSLATE_NOOP("file filter", "BeerXML recipes (*.xml)"); const QByteArray PDF_FILTER = QT_TRANSLATE_NOOP("file filter", "PDF files (*.pdf)"); const QByteArray ALL_FILTER = QT_TRANSLATE_NOOP("file filter", "All files (*)"); const QByteArray OPEN_FILTER = QT_TRANSLATE_NOOP("file filter", FILE_FILTER + ";;" + BEERXML_FILTER + ";;" + ALL_FILTER); const QByteArray SAVE_FILTER = QT_TRANSLATE_NOOP("file filter", FILE_FILTER + ";;" + ALL_FILTER); const QByteArray EXPORT_FILTER = QT_TRANSLATE_NOOP("file filter", HTML_FILTER + ";;" + BEERXML_FILTER + ";;" + PDF_FILTER + ";;" + TEXT_FILTER); const QByteArray DATA_FILE = "qbrewdata"; const QByteArray HELP_FILE = "handbook-index.html"; const QByteArray PRIMER_FILE = "primer.html"; const QByteArray DEFAULT_FILE = QT_TRANSLATE_NOOP("file name", "untitled"); // previous valid data formats const QByteArray RECIPE_PREVIOUS = "0.1.7"; const QByteArray DATA_PREVIOUS = "0.3.0"; /////////////////////////////////////////////////////////////////// // configuration strings const QByteArray CONFGROUP_WINDOW = "/window"; const QByteArray CONF_WIN_STATUSBAR = "/statusbar"; const QByteArray CONF_WIN_MAINWINDOW = "/mainwindow"; const QByteArray CONF_WIN_SIZE = "/size"; const QByteArray CONFGROUP_GENERAL = "/general"; const QByteArray CONF_GEN_LOOK_FEEL = "/lookfeel"; const QByteArray CONF_GEN_SHOW_SPLASH = "/showsplash"; const QByteArray CONF_GEN_AUTOSAVE = "/autosave"; const QByteArray CONF_GEN_SAVEINTERVAL = "/saveinterval"; const QByteArray CONF_GEN_AUTOBACKUP = "/autobackup"; const QByteArray CONF_GEN_LOADLAST = "/loadlast"; const QByteArray CONF_GEN_RECENTFILES = "/recentfiles"; const QByteArray CONF_GEN_RECENTNUM = "/recentnum"; const QByteArray CONFGROUP_RECIPE = "/recipedefaults"; const QByteArray CONF_RECIPE_BATCH = "/batch"; const QByteArray CONF_RECIPE_STYLE = "/style"; const QByteArray CONF_RECIPE_HOPFORM = "/hopform"; // TODO: deprecated 0.4.0 const QByteArray CONF_RECIPE_HOPTYPE = "/hoptype"; const QByteArray CONFGROUP_CALC = "/calc"; const QByteArray CONF_CALC_STEEPYIELD = "/steepyield"; const QByteArray CONF_CALC_EFFICIENCY = "/efficiency"; const QByteArray CONF_CALC_MOREY = "/morey"; const QByteArray CONF_CALC_TINSETH = "/tinseth"; const QByteArray CONF_CALC_UNITS = "/units"; ////////////////////////////////////////////////////////////////////////////// // XML Format Strings (need to be QString) const QString tagRecipe = "recipe"; const QString attrApplication = "application"; const QString attrGenerator = "generator"; const QString tagTitle = "title"; const QString tagBrewer = "brewer"; const QString tagBatch = "batch"; const QString tagNotes = "notes"; const QString attrClass = "class"; const QString classRecipe = tagRecipe; const QString classBatch = tagBatch; const QString tagDoc = "qbrewdata"; const QString attrVersion = "version"; const QString tagStyles = "styles"; const QString tagStyle = "style"; const QString attrOGLow = "oglow"; const QString attrOGHigh = "oghigh"; const QString attrFGLow = "fglow"; const QString attrFGHigh = "fghigh"; const QString attrIBULow = "ibulow"; const QString attrIBUHigh = "ibuhigh"; const QString attrSRMLow = "srmlow"; const QString attrSRMHigh = "srmhigh"; const QString tagGrains = "grains"; const QString tagGrain = "grain"; const QString attrQuantity = "quantity"; const QString attrSize = "size"; // TODO: deprecated const QString attrExtract = "extract"; const QString attrColor = "color"; const QString attrType = "type"; const QString attrUse = "use"; const QString tagHops = "hops"; const QString tagHop = "hop"; const QString attrForm = "form"; const QString attrAlpha = "alpha"; const QString attrTime = "time"; const QString tagMiscs = "miscingredients"; // TODO: rename const QString tagMisc = "miscingredient"; const QString attrNotes = "notes"; const QString tagUtilization = "utilization"; const QString tagEntry = "entry"; const QString attrUtil = "util"; // BeerXML Format Strings // TODO: see if I can use lowercase, converting on the fly during import/export const QString beerXMLVersion = "1"; const QString tagRECIPES = "RECIPES"; const QString tagRECIPE = "RECIPE"; const QString tagNAME = "NAME"; const QString tagVERSION = "VERSION"; const QString tagTYPE = "TYPE"; const QString tagSTYLE = "STYLE"; const QString tagBREWER = "BREWER"; const QString tagBATCHSIZE = "BATCH_SIZE"; const QString tagEFFICIENCY = "EFFICIENCY"; const QString tagFERMENTABLES = "FERMENTABLES"; const QString tagFERMENTABLE = "FERMENTABLE"; const QString tagHOPS = "HOPS"; const QString tagHOP = "HOP"; const QString tagYEASTS = "YEASTS"; const QString tagYEAST = "YEAST"; const QString tagMISCS = "MISCS"; const QString tagMISC = "MISC"; const QString tagNOTES = "NOTES"; const QString tagTASTENOTES = "TASTE_NOTES"; const QString tagWATERS = "WATERS"; const QString tagOGMIN = "OG_MIN"; const QString tagOGMAX = "OG_MAX"; const QString tagFGMIN = "FG_MIN"; const QString tagFGMAX = "FG_MAX"; const QString tagIBUMIN = "IBU_MIN"; const QString tagIBUMAX = "IBU_MAX"; const QString tagCOLORMIN = "COLOR_MIN"; const QString tagCOLORMAX = "COLOR_MAX"; const QString tagAMOUNT = "AMOUNT"; const QString tagAMOUNTISWEIGHT = "AMOUNT_IS_WEIGHT"; const QString tagYIELD = "YIELD"; const QString tagCOLOR = "COLOR"; const QString tagALPHA = "ALPHA"; const QString tagUSE = "USE"; const QString tagTIME = "TIME"; const QString tagFORM = "FORM"; /////////////////////////////////////////////////////////////////// // Misc const QByteArray UNIT_METRIC = QT_TRANSLATE_NOOP("units", "Metric"); const QByteArray UNIT_US = QT_TRANSLATE_NOOP("units", "US"); }; // namespace Resource #endif // RESOURCE_H qbrew-0.4.1/src/recipereader.h0000644000175100017510000000425411016417220015167 0ustar daviddavid/*************************************************************************** recipereader.h ------------------- XML stream reader for recipe ------------------- Copyright 2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef RECIPEREADER_H #define RECIPEREADER_H #include #include class Recipe; class RecipeReader : public QXmlStreamReader { public: // constructor RecipeReader(QIODevice *device); // check for valid recipe format bool isRecipeFormat(); // read xml into recipe bool readRecipe(Recipe *recipe); private: // skip over an element void skipElement(); }; class RecipeWriter : public QXmlStreamWriter { public: // constructor RecipeWriter(QIODevice *device); // write out recipe to xml bool writeRecipe(Recipe *recipe); }; #endif // RECIPEREADER_H qbrew-0.4.1/src/style.cpp0000644000175100017510000000550511016417220014230 0ustar daviddavid/*************************************************************************** style.cpp ------------------- AHA-like style class ------------------- Copyright 1999-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include "resource.h" #include "style.h" using namespace Resource; ////////////////////////////////////////////////////////////////////////////// // Construction, Destruction // ////////////////////////////////////////////////////////////////////////////// Style::Style() : name_("Generic Ale"), oglow_(0.0), oghi_(0.0), fglow_(0.0), fghi_(0.0), ibulow_(0), ibuhi_(100), srmlow_(0), srmhi_(40) { ; } ////////////////////////////////////////////////////////////////////////////// // Style() // ------- // Constructor Style::Style(const QString name, const double &oglow, const double &oghi, const double &fglow, const double &fghi, const int &ibulow, const int &ibuhi, const int &srmlow, const int &srmhi) : name_(name), oglow_(oglow), oghi_(oghi), fglow_(fglow), fghi_(fghi), ibulow_(ibulow), ibuhi_(ibuhi), srmlow_(srmlow), srmhi_(srmhi) { // older qbrewdata files might not have FG values if (fglow_ == 0.0) fglow_ = ((oglow_ - 1.0) * 0.25) + 1.0; if (fghi_ == 0.0) fghi_ = ((oghi_ - 1.0) * 0.25) + 1.0; } Style::Style(const Style &s) : name_(s.name_), oglow_(s.oglow_), oghi_(s.oghi_), fglow_(s.fglow_), fghi_(s.fghi_), ibulow_(s.ibulow_), ibuhi_(s.ibuhi_), srmlow_(s.srmlow_), srmhi_(s.srmhi_) { ; } Style::~Style() { ; } ////////////////////////////////////////////////////////////////////////////// // Miscellaneous // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // operator= // --------- // Assignment operator Style Style::operator=(const Style &s) { if (&s != this) { name_ = s.name_; oglow_ = s.oglow_; oghi_ = s.oghi_; fglow_ = s.fglow_; fghi_ = s.fghi_; ibulow_ = s.ibulow_; ibuhi_ = s.ibuhi_; srmlow_ = s.srmlow_; srmhi_ = s.srmhi_; } return *this; } ////////////////////////////////////////////////////////////////////////////// // operator== // ---------- // Equivalence operator bool Style::operator==(const Style &s) const { return ( (name_ == s.name_) && (oglow_ == s.oglow_) && (oghi_ == s.oghi_) && (fglow_ == s.fglow_) && (fghi_ == s.fghi_) && (ibulow_ == s.ibulow_) && (ibuhi_ == s.ibuhi_) && (srmlow_ == s.srmlow_) && (srmhi_ == s.srmhi_) ); } qbrew-0.4.1/src/graindelegate.h0000644000175100017510000000473211016417220015331 0ustar daviddavid/*************************************************************************** graindelegate.h ------------------- Grain delegate editor ------------------- Copyright 2006-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef GRAINVIEW_H #define GRAINVIEW_H #include // TODO: inherit from QStyleItemDelegate when available in 4.4 class GrainDelegate : public QItemDelegate { Q_OBJECT public: GrainDelegate(QObject *parent = 0); ~GrainDelegate(); // create editor widget QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; // set the contents of the editor void setEditorData(QWidget *editor, const QModelIndex &index) const; // set the contents of the model void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; // manage the editor's geometry void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; #endif // GRAINDELEGATE_H qbrew-0.4.1/src/notepage.cpp0000644000175100017510000000461111016417220014667 0ustar daviddavid/*************************************************************************** notepage.cpp ------------------- A dialog page for notes ------------------- Copyright 2003-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include "recipe.h" #include "notepage.h" ////////////////////////////////////////////////////////////////////////////// // Construction, Destruction // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // NotePage() // ---------- // Constructor NotePage::NotePage(QWidget *parent, Recipe *recipe) : QWidget(parent), recipe_(recipe) { ui.setupUi(this); // set up connections connect(ui.recipeedit, SIGNAL(textChanged()), this, SLOT(updateRecipeNotes())); connect(ui.batchedit, SIGNAL(textChanged()), this, SLOT(updateBatchNotes())); } NotePage::~NotePage() { ; } ////////////////////////////////////////////////////////////////////////////// // refresh() // --------- // initialize void NotePage::refresh() { ui.recipeedit->blockSignals(true); ui.batchedit->blockSignals(true); ui.recipeedit->setPlainText(recipe_->recipeNotes()); ui.batchedit->setPlainText(recipe_->batchNotes()); ui.recipeedit->blockSignals(false); ui.batchedit->blockSignals(false); } ////////////////////////////////////////////////////////////////////////////// // updateRecipeNotes() // ----------------- // Update the notes in the model void NotePage::updateRecipeNotes() { // TODO: this is very inefficient, particularly for large amounts of text // consider making character have a list of paragraphs, then only update // the paragraph that changed. recipe_->setRecipeNotes(ui.recipeedit->toPlainText()); } ////////////////////////////////////////////////////////////////////////////// // updateBatchNotes() // ----------------- // Update the notes in the model void NotePage::updateBatchNotes() { // TODO: this is very inefficient, particularly for large amounts of text // consider making character have a list of paragraphs, then only update // the paragraph that changed. recipe_->setBatchNotes(ui.batchedit->toPlainText()); } qbrew-0.4.1/src/configstate.h0000644000175100017510000000540611016417220015043 0ustar daviddavid/*************************************************************************** configstate.h ------------------- State for config dialog ------------------- Copyright 2006-2008 David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef CONFIGSTATE_H #define CONFIGSTATE_H #include #include "hop.h" #include "resource.h" struct WinConfigState { #if defined (Q_WS_MAC) WinConfigState() : statusbar(true) { ; } #else WinConfigState() : statusbar(false) { ; } #endif bool statusbar; }; struct GenConfigState { GenConfigState() : lookfeel(""), showsplash(true), autosave(true), saveinterval(5), autobackup(true), loadlast(true), recentnum(5), recentfiles() { ; } QString lookfeel; bool showsplash; bool autosave; int saveinterval; bool autobackup; bool loadlast; int recentnum; QStringList recentfiles; }; struct RecipeConfigState { RecipeConfigState() : batch(5.00), style("Generic Ale"), hoptype(Hop::PELLET_STRING) { ; } double batch; QString style; QString hoptype; }; struct CalcConfigState { CalcConfigState() : steepyield(0.5), efficiency(0.75), morey(false), tinseth(false), units(Resource::UNIT_US) { ; } double steepyield; double efficiency; bool morey; bool tinseth; QString units; }; struct ConfigState { WinConfigState window; GenConfigState general; RecipeConfigState recipe; CalcConfigState calc; }; #endif // CONFIGSTATE_H qbrew-0.4.1/src/recipe.h0000644000175100017510000001510711016417220014003 0ustar daviddavid/*************************************************************************** recipe.h ------------------- Recipe (document) class ------------------- Copyright 2001-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef RECIPE_H #define RECIPE_H #include #include #include "quantity.h" #include "grain.h" #include "hop.h" #include "misc.h" #include "style.h" class TextPrinter; class Recipe : public QObject { Q_OBJECT public: // TODO: make sure am comparing lowercase // recipe strings static const QByteArray EXTRACT_STRING; static const QByteArray PARTIAL_STRING; static const QByteArray ALLGRAIN_STRING; // default constructor Recipe(QObject *parent=0); Recipe(const Recipe &recipe); Recipe operator=(const Recipe &recipe); virtual ~Recipe(); // start a new recipe void newRecipe(); // load a recipe bool loadRecipe(const QString &filename); // save the current recipe bool saveRecipe(const QString &filename); // preview the recipe void previewRecipe(TextPrinter *textprinter); // print the recipe void printRecipe(TextPrinter *textprinter); // is the file in native format? bool nativeFormat(const QString &filename); // is the file in BeerXml format? bool beerXmlFormat(const QString &filename); // import a recipe from another BeerXML format bool importBeerXml(const QString &filename); // export the recipe as html bool exportHtml(const QString &filename); // export the recipe to BeerXML format bool exportBeerXML(const QString &filename); // export the recipe as pdf bool exportPdf(TextPrinter *textprinter, const QString &filename); // export the recipe as html bool exportText(const QString &filename); // get and set const QString& title() const; void setTitle(const QString &t); const QString& brewer() const; void setBrewer(const QString &b); const Volume &size() const; void setSize(const Volume &s); const Style &style() const; void setStyle(const Style &s); void setStyle(const QString &s); const QString& recipeNotes() const; void setRecipeNotes(const QString &n); const QString& batchNotes() const; void setBatchNotes(const QString &n); // add an ingredient to the recipe void addGrain(const Grain &g); void addHop(const Hop &h); void addMisc(const Misc &m); // return pointer to ingredient lists GrainList *grains(); HopList *hops(); MiscList *miscs(); // return original gravity double OG(); // return bitterness int IBU(); // return color int SRM(); // return estimated final gravity double FGEstimate(); // return method of recipe QString method(); // return alcohol %v double ABV(); static double ABV(double og, double fg); // return alcohol %w double ABW(); static double ABW(double og, double fg); // convert Specific Gravity to Plato static double SgToP(double sg); // convert grain extract to yield static double extractToYield(double extract); static double yieldToExtract(double yield); // is the recipe modified? bool modified() const; public slots: // recalculate the recipe values void recalc(); // set recipe as modified void setModified(bool m); signals: // send that the recipe has been loaded or created void recipeChanged(); // send that the recipe has been modified void recipeModified(); private: // calculations double calcOG(); int calcSRM(); int calcIBU(); int calcRagerIBU(); int calcTinsethIBU(); const QString recipeHTML(); const QString recipeText(); private: bool modified_; QString title_; QString brewer_; Volume size_; Style style_; GrainList grains_; HopList hops_; MiscList miscs_; QString recipenotes_; QString batchnotes_; double og_; int ibu_; int srm_; }; // Inlined Methods ////////////////////////////////////////////////////////// inline const QString &Recipe::title() const { return title_; } inline void Recipe::setTitle(const QString &t) { title_ = t; setModified(true); } inline const QString &Recipe::brewer() const { return brewer_; } inline void Recipe::setBrewer(const QString &b) { brewer_ = b; setModified(true); } inline const Volume &Recipe::size() const { return size_; } inline void Recipe::setSize(const Volume &s) { size_ = s; recalc(); setModified(true); } inline const Style &Recipe::style() const { return style_; } inline void Recipe::setStyle(const Style &s) { style_ = s; setModified(true); } inline const QString& Recipe::recipeNotes() const { return recipenotes_; } inline void Recipe::setRecipeNotes(const QString &n) { recipenotes_ = n; setModified(true); } inline const QString& Recipe::batchNotes() const { return batchnotes_; } inline void Recipe::setBatchNotes(const QString &n) { batchnotes_ = n; setModified(true); } inline GrainList *Recipe::grains() { return &grains_; } inline HopList *Recipe::hops() { return &hops_; } inline MiscList *Recipe::miscs() { return &miscs_; } inline double Recipe::OG() { return og_; } inline int Recipe::IBU() { return ibu_; } inline int Recipe::SRM() { return srm_; } #endif // RECIPE_H qbrew-0.4.1/src/miscmodel.cpp0000644000175100017510000002203211016417220015036 0ustar daviddavid/*************************************************************************** miscmodel.cpp ------------------- Misc model ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include "data.h" #include "miscmodel.h" using namespace Resource; ////////////////////////////////////////////////////////////////////////////// // MiscModel() // ------------ // Constructor MiscModel::MiscModel(QObject *parent, MiscList *list) : QAbstractTableModel(parent), list_(list) {} MiscModel::~MiscModel() {} ////////////////////////////////////////////////////////////////////////////// // flush() // ------- // Reset the model void MiscModel::flush() { reset(); } ////////////////////////////////////////////////////////////////////////////// // data() // ------ // Return data at index QVariant MiscModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= list_->count()) return QVariant(); // row is the entry in the QList const Misc &misc = list_->at(index.row()); // column is the ingredient "field" if (role == Qt::DisplayRole) { switch (index.column()) { case NAME: return misc.name(); case QUANTITY: return misc.quantity().toString(3); case TYPE: return misc.type(); case NOTES: return misc.notes(); default: return QVariant(); } } else if (role == Qt::EditRole) { switch (index.column()) { case NAME: return misc.name(); case QUANTITY: { // return converted quantity Quantity quantity = misc.quantity(); quantity.convert(Data::instance()->defaultMiscUnit()); return quantity.amount(); } case TYPE: return misc.type(); case NOTES: return misc.notes(); default: return QVariant(); } } else if (role == Qt::TextAlignmentRole) { switch (index.column()) { case NAME: case TYPE: case NOTES: return Qt::AlignLeft; case QUANTITY: default: return Qt::AlignRight; } } else { return QVariant(); } } ////////////////////////////////////////////////////////////////////////////// // setData() // --------- // Set data at index bool MiscModel::setData(const QModelIndex &index, const QVariant &value, int role) { static bool deleting = false; Misc misc; QString name; int row = index.row(); int column = index.column(); if (!index.isValid()) return false; if (role != Qt::EditRole) return false; if (row >= list_->count()) return false; // grab existing misc if (row < list_->count()) misc = list_->value(row); switch (column) { case NAME: // editing name as several special cases name = value.toString(); // deleting name deletes ingredient if (name.isEmpty()) { if (row >= list_->count()) return false; // empty // TODO: for some reason this gets entered recursively... if (deleting) return false; deleting = true; int status = QMessageBox::question(QApplication::activeWindow(), TITLE + tr(" - Delete?"), tr("Do you wish to remove this entry?"), QMessageBox::Yes | QMessageBox::Cancel); if (status == QMessageBox::Yes) { // remove misc beginRemoveRows(index.parent(), row, row); list_->removeAt(row); emit modified(); endRemoveRows(); deleting = false; return true; } else { // ignore deleting = false; return false; } } // no change, nothing to do if (name == list_->at(row).name()) { return false; } // changed name misc.setName(name); if (Data::instance()->hasMisc(name)) { Misc newmisc = Data::instance()->misc(name); // we don't override weight misc.setType(newmisc.type()); misc.setNotes(newmisc.notes()); } break; case QUANTITY: misc.setQuantity(Quantity(value.toDouble(), Data::instance()->defaultMiscUnit())); break; case TYPE: misc.setType(value.toString()); break; case NOTES: misc.setNotes(value.toString()); break; default: return false; } list_->replace(row, misc); emit modified(); // whole row may have changed emit dataChanged(index.sibling(row, NAME), index.sibling(row, NOTES)); return true; } ////////////////////////////////////////////////////////////////////////////// // insertRows() // ------------ // Insert rows into table bool MiscModel::insertRows(int row, int count, const QModelIndex&) { if (count != 1) return false; // only insert one row at a time if ((row < 0) || (row >= list_->count())) row = list_->count(); Misc misc = Data::instance()->misc(tr("Generic")); beginInsertRows(QModelIndex(), row, row); list_->insert(row, misc); emit modified(); endInsertRows(); return true; } ////////////////////////////////////////////////////////////////////////////// // removeRows() // ------------ // Remove rows from table bool MiscModel::removeRows(int row, int count, const QModelIndex&) { if (count != 1) return false; // only remove one row at a time if ((row < 0) || (row >= list_->count())) return false; int status = QMessageBox::question(QApplication::activeWindow(), TITLE + tr(" - Delete?"), tr("Do you wish to remove this entry?"), QMessageBox::Yes | QMessageBox::Cancel); if (status == QMessageBox::Cancel) { return false; } beginRemoveRows(QModelIndex(), row, row); list_->removeAt(row); emit modified(); endRemoveRows(); return true; } ////////////////////////////////////////////////////////////////////////////// // headerData() // ------------ // Return header information QVariant MiscModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NAME: return tr("Misc"); case QUANTITY: return tr("Quantity"); case TYPE: return tr("Type"); case NOTES: return tr("Notes"); default: return QVariant(); } } return QVariant(); } ////////////////////////////////////////////////////////////////////////////// // flags() // ------- // Return flags at index Qt::ItemFlags MiscModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } ////////////////////////////////////////////////////////////////////////////// // rowCount() // ---------- // Return number of rows of data int MiscModel::rowCount(const QModelIndex &) const { return list_->count(); } ////////////////////////////////////////////////////////////////////////////// // columnCount() // ------------- // Return number of columns of data int MiscModel::columnCount(const QModelIndex &) const { return COUNT; } ////////////////////////////////////////////////////////////////////////////// // sort() // ------ // Sort by column void MiscModel::sort(int column, Qt::SortOrder order) { QList > sortlist; foreach(Misc misc, *list_) { QString field; switch (column) { case NAME: field = misc.name(); break; case QUANTITY: field = QString::number(misc.quantity().amount()).rightJustified(8,'0'); break; case TYPE: field = misc.type(); break; case NOTES: default: field = misc.notes(); break; } sortlist.append(QPair(field, misc)); } // sort list qSort(sortlist.begin(), sortlist.end()); emit layoutAboutToBeChanged(); // create new list list_->clear(); QPair pair; foreach(pair, sortlist) { if (order == Qt::AscendingOrder) list_->append(pair.second); else list_->prepend(pair.second); } emit layoutChanged(); } qbrew-0.4.1/src/quantity.cpp0000644000175100017510000002745611016417220014757 0ustar daviddavid/*************************************************************************** quantity.h ------------------- Generic quantity class ------------------- Copyright 2001-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include "quantity.h" // Unit Class /////////////////////////////////////////////////////////////// // our static map units QMap Unit::units_; /** * Full constructor. Construct a Unit from name and symbol * and add to map. */ Unit::Unit(const QString &name, const QString &symbol) : name_(name), symbol_(symbol), conversions_() { units_.insert(symbol_, this); } /** * Destructor. Removes Unit from map. */ inline Unit::~Unit() { units_.remove(symbol_); } /** * Add a conversion function for this Unit. */ void Unit::addConversion(const Unit &u, const Conversion c) { conversions_.insert(u.name(), c); } /** * Convert to amount of other units to this Unit. */ double Unit::convert(double amount, const Unit &other) const { // if converting from self... if (other.name_ == name_) return amount; if (conversions_.contains(other.name())) { return (conversions_.value(other.name()))(amount); } // TODO: throw exception instead of returning 0.0 return 0.0; } /** * Return Unit associated with symbol. */ Unit* Unit::unit(const QString &symbol) { if (units_.contains(symbol)) return units_.value(symbol); else return 0; } /** * Equivalence operator. */ bool Unit::operator==(const Unit &u) const { return ((name_ == u.name_) && (symbol_ == u.symbol_)); } // Quantity Class /////////////////////////////////////////////////////////// Unit Quantity::generic("unit", "unit"); /** * Assignment operator. */ Quantity &Quantity::operator=(const Quantity &q) { if (this != &q) { amount_ = q.amount_; unit_ = q.unit_; } return *this; } /** * Equivalence operator. */ bool Quantity::operator==(const Quantity &q) const { return ((amount_ == q.amount_) && (unit_->symbol() == q.unit_->symbol())); } /** * Convert Quantity to unit. */ void Quantity::convert(const Unit &u) { amount_ = unit_->convert(amount_, u); unit_ = &u; // TODO: change to bool return, and return false if not convertable } /** * Output quantity as a string, output is "xx.xx symbol". */ QString Quantity::toString(int prec) const { return (QString::number(amount_, 'f', prec) + " " + unit_->symbol()); } /** * Set quantity from string (assumes format of "%f %s") */ void Quantity::fromQString(const QString &from, const Unit &defaultunit) { QString str = from.section(' ', 1, -1); unit_ = Unit::unit(str); if (!unit_) unit_ = &defaultunit; str = from.section(' ', 0, 0); amount_ = str.toDouble(); } // Addition operators ///////////////// Quantity &Quantity::operator+=(const Quantity &q) { if (unit_->convertable(*q.unit_)) { amount_ += q.unit_->convert(q.amount_, *unit_); } return *this; } Quantity operator+(const Quantity &q1, const Quantity &q2) { Quantity temp(q1); if (temp.unit_->convertable(*q2.unit_)) { temp.amount_ += q2.unit_->convert(q2.amount_, *temp.unit_); } return temp; } Quantity operator+(const Quantity &q, const double d) { Quantity temp(q); temp.amount_ += d; return temp; } // Subtraction operators ////////////// Quantity &Quantity::operator-=(const Quantity &q) { if (unit_->convertable(*q.unit_)) { amount_ -= q.unit_->convert(q.amount_, *unit_); } return *this; } Quantity operator-(const Quantity &q1, const Quantity &q2) { Quantity temp(q1); if (temp.unit_->convertable(*q2.unit_)) { temp.amount_ += q2.unit_->convert(q2.amount_, *temp.unit_); } return temp; } Quantity operator-(const Quantity &q, const double d) { Quantity temp(q); temp.amount_ -= d; return temp; } Quantity operator-(const double d, const Quantity &q) { Quantity temp(q); temp.amount_ = d - temp.amount_; return temp; } // Misc operators ///////////////////// Quantity operator-(const Quantity &q) { Quantity temp(q); temp.amount_ *= -1.0; return temp; } // Weight Class ////////////////////////////////////////////////////////// Unit Weight::gram("gram", "g"); Unit Weight::kilogram("kilogram", "kg"); Unit Weight::ounce("ounce", "oz"); Unit Weight::pound("pound", "lb"); bool Weight::initialized_ = false; /** * Constructor. */ Weight::Weight(double amount, const Unit &unit) : Quantity(amount, unit) { if (!initialized_) initialize(); } /** * Conversion constructor. Converts from base Quantity. */ Weight::Weight(const Quantity &q) : Quantity(q) { if (!initialized_) initialize(); // TODO: throw if nonconvertable } /** * Initialize the class units. */ void Weight::initialize() { gram.addConversion(kilogram, gram2kilogram); gram.addConversion(ounce, gram2ounce); gram.addConversion(pound, gram2pound); kilogram.addConversion(gram, kilogram2gram); kilogram.addConversion(ounce, kilogram2ounce); kilogram.addConversion(pound, kilogram2pound); ounce.addConversion(gram, ounce2gram); ounce.addConversion(kilogram, ounce2kilogram); ounce.addConversion(pound, ounce2pound); pound.addConversion(gram, pound2gram); pound.addConversion(kilogram, pound2kilogram); pound.addConversion(ounce, pound2ounce); initialized_ = true; } // Volume Class ////////////////////////////////////////////////////////// Unit Volume::barrel("barrel", "bbl"); Unit Volume::gallon("gallon", "gal"); Unit Volume::hectoliter("hectoliter", "hL"); Unit Volume::liter("liter", "L"); Unit Volume::milliliter("milliliter", "mL"); Unit Volume::fluidounce("fluid ounce", "fl oz"); bool Volume::initialized_ = false; /** * Constructor. */ Volume::Volume(double amount, const Unit &unit) : Quantity(amount, unit) { if (!initialized_) initialize(); } /** * Conversion constructor. Converts from base Quantity. */ Volume::Volume(const Quantity &q) : Quantity(q) { if (!initialized_) initialize(); // TODO: throw if nonconvertable } /** * Initialize the class units. */ void Volume::initialize() { barrel.addConversion(fluidounce, barrel2fluidounce); barrel.addConversion(gallon, barrel2gallon); barrel.addConversion(hectoliter, barrel2hectoliter); barrel.addConversion(liter, barrel2liter); barrel.addConversion(milliliter, barrel2milliliter); fluidounce.addConversion(barrel, fluidounce2barrel); fluidounce.addConversion(gallon, fluidounce2gallon); fluidounce.addConversion(hectoliter, fluidounce2hectoliter); fluidounce.addConversion(liter, fluidounce2liter); fluidounce.addConversion(milliliter, fluidounce2milliliter); gallon.addConversion(barrel, gallon2barrel); gallon.addConversion(fluidounce, gallon2fluidounce); gallon.addConversion(hectoliter, gallon2hectoliter); gallon.addConversion(liter, gallon2liter); gallon.addConversion(milliliter, gallon2milliliter); hectoliter.addConversion(barrel, hectoliter2barrel); hectoliter.addConversion(fluidounce, hectoliter2fluidounce); hectoliter.addConversion(gallon, hectoliter2gallon); hectoliter.addConversion(liter, hectoliter2liter); hectoliter.addConversion(milliliter, hectoliter2milliliter); liter.addConversion(barrel, liter2barrel); liter.addConversion(fluidounce, liter2fluidounce); liter.addConversion(gallon, liter2gallon); liter.addConversion(hectoliter, liter2hectoliter); liter.addConversion(milliliter, liter2milliliter); milliliter.addConversion(barrel, milliliter2barrel); milliliter.addConversion(fluidounce, milliliter2fluidounce); milliliter.addConversion(gallon, milliliter2gallon); milliliter.addConversion(hectoliter, milliliter2hectoliter); milliliter.addConversion(liter, milliliter2liter); initialized_ = true; } // Temperature Class ////////////////////////////////////////////////////////// Unit Temperature::fahrenheit("fahrenheit", "F"); Unit Temperature::celsius("celsius", "C"); bool Temperature::initialized_ = false; /** * Constructor */ Temperature::Temperature(double amount, const Unit &unit) : Quantity(amount, unit) { if (!initialized_) initialize(); } /** * Conversion constructor. Converts from base Quantity. */ Temperature::Temperature(const Quantity &q) : Quantity(q) { if (!initialized_) initialize(); // TODO: throw if nonconvertable } /** * Initialize the class units. */ void Temperature::initialize() { fahrenheit.addConversion(celsius, fahrenheit2celsius); celsius.addConversion(fahrenheit, celsius2fahrenheit); } // Conversion Functions ////////////////////////////////////////////////// // TODO: double check formulas double gram2kilogram(double value) { return value / 1000.0; } double gram2ounce(double value) { return value / 28.349523125; } double gram2pound(double value) { return value / 453.59237; } double kilogram2gram(double value) { return value * 1000.0 ; } double kilogram2ounce(double value) { return value * 35.27396; } double kilogram2pound(double value) { return value * 2.2046226; } double ounce2gram(double value) { return value * 28.349523125; } double ounce2kilogram(double value) { return value / 35.27396; } double ounce2pound(double value) { return value / 16.0; } double pound2gram(double value) { return value * 453.59237; } double pound2kilogram(double value) { return value / 2.2046226; } double pound2ounce(double value) { return value * 16.0; } double barrel2fluidounce(double value) { return value * 3968.0; } double barrel2gallon(double value) { return value * 31.0; } double barrel2hectoliter(double value) { return value * 1.173477658; } double barrel2liter(double value) { return value * 117.3477658; } double barrel2milliliter(double value) { return value * 117347.7658; } double fluidounce2barrel(double value) { return value / 3968.0; } double fluidounce2gallon(double value) { return value / 128.0; } double fluidounce2hectoliter(double value) { return value / 3381.4016; } double fluidounce2liter(double value) { return value / 33.814016; } double fluidounce2milliliter(double value) { return value * 29.5735; } double gallon2barrel(double value) { return value / 31.0; } double gallon2fluidounce(double value) { return value * 128.0; } double gallon2hectoliter(double value) { return value / 26.4172; } double gallon2liter(double value) { return value * 3.7854118; } double gallon2milliliter(double value) { return value * 3785.4118; } double hectoliter2barrel(double value) { return value / 1.173477658; } double hectoliter2fluidounce(double value) { return value * 3381.4016; } double hectoliter2gallon(double value) { return value * 26.4172; } double hectoliter2liter(double value) { return value * 100.0; } double hectoliter2milliliter(double value) { return value * 100000.0; } double liter2barrel(double value) { return value / 117.3477658; } double liter2fluidounce(double value) { return value * 33.814016; } double liter2gallon(double value) { return value / 3.7854118; } double liter2hectoliter(double value) { return value / 100.0; } double liter2milliliter(double value) { return value * 1000.0; } double milliliter2barrel(double value) { return value / 117347.7658; } double milliliter2fluidounce(double value) { return value / 29.5735; } double milliliter2gallon(double value) { return value / 3785.4118; } double milliliter2hectoliter(double value) { return value / 100000.0; } double milliliter2liter(double value) { return value / 1000.0; } double fahrenheit2celsius(double value) { return (value - 32.0) / 1.8; } double celsius2fahrenheit(double value) { return (value * 1.8) + 32.0; } qbrew-0.4.1/src/alcoholtool.cpp0000644000175100017510000000305411016417220015404 0ustar daviddavid/*************************************************************************** alcoholtool.cpp ------------------- An Alcohol Percentage Calculator utility for QBrew ------------------- Copyright 2004-2008, David Johnson Based on code Copyright 2004, Michal Palczewski Please see the header file for copyright and license information ***************************************************************************/ #include #include "recipe.h" #include "resource.h" #include "alcoholtool.h" ////////////////////////////////////////////////////////////////////////////// // AbvcalcTool() // ---------------- // Constructor AlcoholTool::AlcoholTool(QWidget* parent) : QDialog(parent) { setWindowTitle(Resource::TITLE + tr(" - Alcohol Tool")); ui.setupUi(this); // connections connect(ui.og, SIGNAL(valueChanged(double)), this, SLOT(recalc())); connect(ui.fg, SIGNAL(valueChanged(double)), this, SLOT(recalc())); recalc(); } ////////////////////////////////////////////////////////////////////////////// // recalc() // -------- // the signal to calculate the Alcohol percentage void AlcoholTool::recalc() { ui.abw->setText(QString::number(Recipe::ABW(ui.og->value(), ui.fg->value()) * 100.0, 'f', 1) + "%"); ui.abv->setText(QString::number(Recipe::ABV(ui.og->value(), ui.fg->value()) * 100.0, 'f', 1) + "%"); } qbrew-0.4.1/src/qbrew.cpp0000755000175100017510000012052411016417220014212 0ustar daviddavid/*************************************************************************** qbrew.cpp ------------------- Control class for QBrew ------------------- Copyright 2006-2008 David Johnson Please see the header file for copyright and license information. ***************************************************************************/ #include #include "alcoholtool.h" #include "configure.h" #include "data.h" #include "databasetool.h" #include "recipe.h" #include "helpviewer.h" #include "hydrometertool.h" #include "resource.h" #include "textprinter.h" #include "view.h" #include "qbrew.h" using namespace Resource; QBrew *QBrew::instance_ = 0; static QMutex instancelock; ////////////////////////////////////////////////////////////////////////////// // Construction, Destruction, Initialization // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // QBrew() // ------------ // Private constructor QBrew::QBrew() : data_(0), recipe_(0), view_(0), alcoholtool_(0), databasetool_(0), hydrometertool_(0), configure_(0), handbook_(0), primer_(0), state_(), filename_(), newflag_(false), backed_(false), autosave_(0), autosavename_(), textprinter_(0) { ; } ////////////////////////////////////////////////////////////////////////////// // initialize() // ------------ // Initialize mainwindow void QBrew::initialize(const QString &filename) { setAttribute(Qt::WA_DeleteOnClose); readConfig(); // show splashscreen if (state_.general.showsplash && QFile::exists(dataBase() + tr("splash.png"))) { QSplashScreen *splash = new QSplashScreen(dataBase() + tr("splash.png"), Qt::WindowStaysOnTopHint); splash->show(); QApplication::flush(); QTimer::singleShot(3000, splash, SLOT(close())); } // setup look and feel setWindowIcon(QIcon(":/pics/qbrew.png")); if ((state_.general.lookfeel != QApplication::style()->objectName()) && (QStyleFactory::keys().contains(state_.general.lookfeel))) { QApplication::setStyle(state_.general.lookfeel); } // setup UI ui.setupUi(this); initActions(); #if defined (Q_WS_MAC) // initial defaults for mac setUnifiedTitleAndToolBarOnMac(false); ui.maintoolbar->setVisible(false); #endif // restore window state restoreState(); // initialize data data_ = Data::instance(); data_->initialize(state_); // initialize calculations data_->setSteepYield(state_.calc.steepyield); data_->setTinseth(state_.calc.tinseth); data_->setMorey(state_.calc.morey); // create recipe recipe_ = new Recipe(this); connect(recipe_, SIGNAL(recipeModified()), this, SLOT(recipeModified())); // create view view_ = new View(this, recipe_); // load or create a recipe bool emptyfilename = (filename.isEmpty() || (filename == tr(DEFAULT_FILE))); if (emptyfilename && (state_.general.loadlast && !state_.general.recentfiles.isEmpty())) { if (QFile::exists(state_.general.recentfiles.first())) { filename_ = state_.general.recentfiles.first(); } } else { filename_ = filename; } emptyfilename = (filename_.isEmpty() || (filename_ == tr(DEFAULT_FILE))); if (!emptyfilename) { openFile(filename_); } else { recipe_->newRecipe(); newflag_ = true; backed_ = false; filename_ = tr(DEFAULT_FILE) + "." + FILE_EXT; setWindowTitle(TITLE + " " + VERSION + " [*]"); } // connections connect(ui.menuopenrecent, SIGNAL(aboutToShow()), this, SLOT(recentMenuShow())); // other initialization initAutoSave(); setCentralWidget(view_); view_->refresh(); textprinter_ = new TextPrinter(this); } ////////////////////////////////////////////////////////////////////////////// // ~QBrew() // ------------- // Private destructor QBrew::~QBrew() { saveState(); // make sure any open windows are closed if (alcoholtool_) alcoholtool_->close(); if (hydrometertool_) hydrometertool_->close(); if (databasetool_) databasetool_->close(); if (configure_) configure_->close(); if (handbook_) handbook_->close(); if (primer_) primer_->close(); } ////////////////////////////////////////////////////////////////////////////// // instance() // ---------- // Return pointer to the instance QBrew *QBrew::instance() { if (!instance_) { QMutexLocker lock(&instancelock); if (!instance_) instance_ = new QBrew(); } return instance_; } ////////////////////////////////////////////////////////////////////////////// // initActions() // ------------- // Initialize the actions void QBrew::initActions() { connect(ui.actionfilenew, SIGNAL(triggered()), this, SLOT(fileNew())); connect(ui.actionfileopen, SIGNAL(triggered()), this, SLOT(fileOpen())); connect(ui.actionfilesave, SIGNAL(triggered()), this, SLOT(fileSave())); connect(ui.actionfilesaveas, SIGNAL(triggered()), this, SLOT(fileSaveAs())); connect(ui.actionfileprintpreview, SIGNAL(triggered()), this, SLOT(filePrintPreview())); connect(ui.actionfileprint, SIGNAL(triggered()), this, SLOT(filePrint())); connect(ui.actionfileexport, SIGNAL(triggered()), this, SLOT(fileExport())); connect(ui.actionexit, SIGNAL(triggered()), qApp, SLOT(closeAllWindows())); connect(ui.actionalcoholtool, SIGNAL(triggered()), this, SLOT(toolsAlcohol())); connect(ui.actionhydrometertool, SIGNAL(triggered()), this, SLOT(toolsHydrometer())); connect(ui.actiondatabasetool, SIGNAL(triggered()), this, SLOT(toolsDatabase())); connect(ui.actiontogglestatusbar, SIGNAL(toggled(bool)), this, SLOT(optionsStatusbar(bool))); connect(ui.actionconfigure, SIGNAL(triggered()), this, SLOT(optionsConfigure())); connect(ui.actionhelpcontents, SIGNAL(triggered()), this, SLOT(helpContents())); connect(ui.actionprimer, SIGNAL(triggered()), this, SLOT(helpPrimer())); connect(ui.actioncontexthelp, SIGNAL(triggered()), this, SLOT(helpContext())); connect(ui.actionabout, SIGNAL(triggered()), this, SLOT(helpAbout())); #if (QT_VERSION < QT_VERSION_CHECK(4, 4, 0)) ui.actionfileprintpreview->setVisible(false); #endif // insert toolbar toggle action ui.maintoolbar->toggleViewAction()->setStatusTip(tr("Toggle the toolbar")); ui.maintoolbar->toggleViewAction()->setWhatsThis(tr("Enable or disable the Toolbar")); ui.menuoptions->insertAction(ui.actiontogglestatusbar, ui.maintoolbar->toggleViewAction()); } ////////////////////////////////////////////////////////////////////////////// // initAutoSave() // --------------- // Initialize the autosave timer void QBrew::initAutoSave() { // destroy existing timer, if any if (autosave_) { autosave_->stop(); disconnect(autosave_, SIGNAL(timeout()), this, SLOT(autoSave())); delete autosave_; autosave_ = 0; } autosavename_ = QDir::currentPath() + tr("/autosave.") + FILE_EXT; if (state_.general.autosave) { autosave_ = new QTimer(this); connect(autosave_, SIGNAL(timeout()), this, SLOT(autoSave())); autosave_->start(state_.general.saveinterval * 60000); } } ////////////////////////////////////////////////////////////////////////////// // File Menu Implementation // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // fileNew() // --------- // Create a new recipe void QBrew::fileNew() { if (recipe_->modified()) { // query user to save file switch (querySave()) { case QMessageBox::Yes: fileSave(); break; case QMessageBox::No: break; case QMessageBox::Cancel: statusBar()->showMessage(tr("Canceled..."), 2000); // exit function return; } } // create a new file recipe_->newRecipe(); newflag_ = true; backed_ = false; ui.actionfilesave->setEnabled(true); filename_ = tr(DEFAULT_FILE); setFileCaption(filename_); statusBar()->showMessage(tr("Created new recipe"), 2000); } ////////////////////////////////////////////////////////////////////////////// // fileOpen() // ---------- // Open a recipe void QBrew::fileOpen(const QString &filename) { if (recipe_->modified()) { // query user to save file switch (querySave()) { case QMessageBox::Yes: fileSave(); break; case QMessageBox::No: break; case QMessageBox::Cancel: statusBar()->showMessage(tr("Canceled..."), 2000); return; } } // open the file statusBar()->showMessage(tr("Opening recipe...")); QString fname; if (filename.isEmpty()) { fname = QFileDialog::getOpenFileName(this, tr("Open..."), QString(), tr(OPEN_FILTER)); } else { fname = filename; } if (openFile(fname)) { statusBar()->showMessage(tr("Loaded recipe"), 2000); } else { statusBar()->showMessage(tr("Load failed"), 2000); } } ////////////////////////////////////////////////////////////////////////////// // fileRecent() // ----------- // Selection has been made from recent file menu void QBrew::fileRecent() { QAction *action = qobject_cast(sender()); if (!action) return; if (recipe_->modified()) { // file needs to be saved, what do we do switch (querySave()) { case QMessageBox::Yes: fileSave(); break; case QMessageBox::No: break; case QMessageBox::Cancel: statusBar()->showMessage(tr("Canceled..."), 2000); return; } } // open the file QString fname = action->text(); if (openFile(fname)) { statusBar()->showMessage(tr("Loaded recipe"), 2000); addRecent(fname); } } ////////////////////////////////////////////////////////////////////////////// // fileSave() // ---------- // Save a recipe void QBrew::fileSave() { if (newflag_) { fileSaveAs(); } else { if (state_.general.autobackup) { if (!backupFile()) QMessageBox::warning(this, TITLE, tr("%1 was unable to make a backup of %2") .arg(PACKAGE).arg(filename_)); } if (saveFile(filename_)) { // successful in saving file newflag_ = false; ui.actionfilesave->setEnabled(false); setFileCaption(filename_); statusBar()->showMessage(tr("Saved recipe"), 2000); } else { // error in saving file QMessageBox::warning(this, TITLE, tr("%1 was unable to save %2") .arg(PACKAGE).arg(filename_)); statusBar()->showMessage(tr("Error in saving recipe"), 2000); } } } ////////////////////////////////////////////////////////////////////////////// // fileSaveAs() // ------------ // Save a recipe under a new name void QBrew::fileSaveAs() { statusBar()->showMessage(tr("Saving recipe under new filename...")); QString selected; QString fname; fname = QFileDialog::getSaveFileName(this, tr("Save As..."), QString(), tr(SAVE_FILTER), &selected, QFileDialog::DontConfirmOverwrite); if (!fname.isEmpty()) { // we got a filename // add suffix if there is none if (!QFile::exists(fname)) { if (fname.indexOf('.') == -1) fname += '.' + FILE_EXT; } if (state_.general.autobackup) backupFile(); if (QFile::exists(fname)) { // overwrite? switch (queryOverwrite(fname)) { case QMessageBox::Yes: break; case QMessageBox::Cancel: statusBar()->showMessage(tr("Canceled..."), 2000); return; } } if (saveFile(fname)) { // successfully saved newflag_ = false; addRecent(fname); ui.actionfilesave->setEnabled(false); setFileCaption(fname); statusBar()->showMessage(tr("Saved recipe"), 2000); // save name of file filename_ = fname; } else { // error in saving QMessageBox::warning(this, TITLE, tr("Unable to save to %1") .arg(QFileInfo(filename_).fileName())); statusBar()->showMessage(tr("Error in saving recipe"), 2000); } } else { // no file chosen statusBar()->showMessage(tr("Saving aborted"), 2000); } } ////////////////////////////////////////////////////////////////////////////// // fileExport() // ------------ // Export recipe to non-native format void QBrew::fileExport() { bool status = false; QString selected; QString fname; QString dirfile; fname = QFileDialog::getSaveFileName(this, tr("Export..."), QString(), tr(EXPORT_FILTER), &selected, QFileDialog::DontConfirmOverwrite); if (!fname.isEmpty()) { // we got a filename // add suffix if there is none if (!QFile::exists(fname)) { if (fname.indexOf('.') == -1) { if (selected == tr(HTML_FILTER)) fname += ".html"; else if (selected == tr(BEERXML_FILTER)) fname += ".xml"; else if (selected == tr(PDF_FILTER)) fname += ".pdf"; else if (selected == tr(TEXT_FILTER)) fname += ".txt"; } } if (QFile::exists(fname)) { // overwrite? switch (queryOverwrite(fname)) { case QMessageBox::Yes: break; case QMessageBox::Cancel: statusBar()->showMessage(tr("Canceled..."), 2000); return; } } // export recipe if (selected == tr(HTML_FILTER)) { status = recipe_->exportHtml(fname); } else if (selected == tr(BEERXML_FILTER)) { status = recipe_->exportBeerXML(fname); } else if (selected == tr(PDF_FILTER)) { status = recipe_->exportPdf(textprinter_, fname); } else if (selected == tr(TEXT_FILTER)) { status = recipe_->exportText(fname); } if (status) { // successfully exported statusBar()->showMessage(tr("Exported recipe"), 2000); } else { // error in exporting QMessageBox::warning(this, TITLE, tr("Unable to export to %1") .arg(QFileInfo(filename_).fileName())); statusBar()->showMessage(tr("Error exporting recipe"), 2000); } } else { // no file chosen statusBar()->showMessage(tr("Export aborted"), 2000 ); } } ////////////////////////////////////////////////////////////////////////////// // filePrintPreview() // ------------ // Print the recipe void QBrew::filePrintPreview() { statusBar()->showMessage(tr("Print preview...")); recipe_->previewRecipe(textprinter_); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // filePrint() // ------------ // Print the recipe void QBrew::filePrint() { statusBar()->showMessage(tr("Printing...")); recipe_->printRecipe(textprinter_); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // Tools Menu Implementation // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // toolsAlcohol() // -------------- // A utility dialog for alchohol calculation void QBrew::toolsAlcohol() { if (!alcoholtool_) alcoholtool_ = new AlcoholTool(this); alcoholtool_->show(); alcoholtool_->raise(); if (alcoholtool_->isMinimized()) alcoholtool_->showNormal(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // toolsHydrometer() // ----------------- // A utility dialog for hydrometer correction void QBrew::toolsHydrometer() { if (!hydrometertool_) hydrometertool_ = new HydrometerTool(this); hydrometertool_->show(); hydrometertool_->raise(); if (hydrometertool_->isMinimized()) hydrometertool_->showNormal(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // toolsDatabase() // --------------- // A database editor for ingredients void QBrew::toolsDatabase() { if (!databasetool_) databasetool_ = new DatabaseTool(this); databasetool_->show(); databasetool_->raise(); if (databasetool_->isMinimized()) databasetool_->showNormal(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // Options Menu Implementation // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // optionsSatusbar() // ----------------- // Toggle statusbar status void QBrew::optionsStatusbar(bool on) { statusBar()->setVisible(on); state_.window.statusbar = on; statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // optionsConfigure() // ------------ // Display the configuration dialog void QBrew::optionsConfigure() { statusBar()->showMessage(tr("Configuring %1 ...").arg(PACKAGE), 2000); if (!configure_) { configure_ = new Configure(this); connect(configure_, SIGNAL(generalApply(const GenConfigState&)), this, SLOT(applyGeneralState(const GenConfigState&))); connect(configure_, SIGNAL(recipeApply(const RecipeConfigState&)), this, SLOT(applyRecipeState(const RecipeConfigState&))); connect(configure_, SIGNAL(calcApply(const CalcConfigState&)), this, SLOT(applyCalcState(const CalcConfigState&))); connect(configure_, SIGNAL(configureAccept()), this, SLOT(saveConfig())); } if (!configure_->isVisible()) { configure_->setState(state_); } configure_->show(); // non-modal configure_->raise(); if (configure_->isMinimized()) configure_->showNormal(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // Help Menu Implementation // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // helpContents() // -------------- // Display the application manual void QBrew::helpContents() { QString home = docBase() + tr(HELP_FILE); if (!handbook_) handbook_ = new HelpViewer(home, 0); handbook_->show(); handbook_->raise(); if (handbook_->isMinimized()) handbook_->showNormal(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // helpPrimer() // -------------- // Display the brewing primer void QBrew::helpPrimer() { QString home = docBase() + tr(PRIMER_FILE); if (!primer_) primer_ = new HelpViewer(home, 0); primer_->show(); primer_->raise(); if (primer_->isMinimized()) primer_->showNormal(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // helpContext() // ------------- // Display context help cursor void QBrew::helpContext() { QWhatsThis::enterWhatsThisMode(); } ////////////////////////////////////////////////////////////////////////////// // helpAbout() // ----------- // Display the About dialog void QBrew::helpAbout() { QString message; message = "
" + TITLE + tr(" Version ") + VERSION + "
"; message += "

" + tr(DESCRIPTION) + "

"; message += "

" + tr(COPYRIGHT) + ' ' + AUTHOR + "

"; message += tr("

Contributions by ") + CONTRIBUTORS + "

"; QMessageBox::about(this, tr("About ") + TITLE, message); } ////////////////////////////////////////////////////////////////////////////// // Settings // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // applyGeneralState() // ------------------- // Set the general configuration state void QBrew::applyGeneralState(const GenConfigState &state) { GenConfigState oldstate = state_.general; state_.general = state; // apply immediate state changes if (state.lookfeel != oldstate.lookfeel) { QApplication::setStyle(state.lookfeel); } if ((state.autosave != oldstate.autosave) || (state.saveinterval != oldstate.saveinterval)) { initAutoSave(); } if (state.recentnum < oldstate.recentnum) { while (state_.general.recentfiles.count() > state_.general.recentnum) { state_.general.recentfiles.removeLast(); } } statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // applyRecipeState() // ------------------ // Set the recipe configuration state void QBrew::applyRecipeState(const RecipeConfigState &state) { RecipeConfigState oldstate = state_.recipe; state_.recipe = state; // apply state changes if (state.batch != oldstate.batch) { if (state_.calc.units == UNIT_METRIC) { data_->setDefaultSize(Volume(state.batch, Volume::liter)); } else if (state_.calc.units == UNIT_US) { data_->setDefaultSize(Volume(state.batch, Volume::gallon)); } } if (state.style != oldstate.style) { data_->setDefaultStyle(state.style); } if (state.hoptype != oldstate.hoptype) { data_->setDefaultHopType(state.hoptype); } statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // applyCalcState() // ---------------- // Set the calc configuration state void QBrew::applyCalcState(const CalcConfigState &state) { CalcConfigState oldstate = state_.calc; state_.calc = state; // apply state changes if (state.steepyield != oldstate.steepyield) { data_->setSteepYield(state.steepyield); } if (state.efficiency != oldstate.efficiency) { data_->setEfficiency(state.efficiency); } if (state.morey != oldstate.morey) { data_->setMorey(state.morey); } if (state.tinseth != oldstate.tinseth) { data_->setTinseth(state.tinseth); } if (state.units != oldstate.units) { if (state.units == UNIT_METRIC) { data_->setDefaultSize(Volume(state_.recipe.batch, Volume::liter)); data_->setDefaultGrainUnit(Weight::kilogram); data_->setDefaultHopUnit(Weight::gram); data_->setDefaultTempUnit(Temperature::celsius); } else if (state.units == UNIT_US) { data_->setDefaultSize(Volume(state_.recipe.batch, Volume::gallon)); data_->setDefaultGrainUnit(Weight::pound); data_->setDefaultHopUnit(Weight::ounce); data_->setDefaultTempUnit(Temperature::fahrenheit); } // delete hydrometer tool if (hydrometertool_) { delete hydrometertool_; hydrometertool_ = 0; } } // recalculate recipe_->recalc(); view_->flush(); statusBar()->showMessage(tr(READY), 2000); } ////////////////////////////////////////////////////////////////////////////// // saveConfig() // ----------- // Save the configuration (when OK pressed in dialog) void QBrew::saveConfig() { writeConfig(); statusBar()->showMessage(tr("Saved configuration"), 2000); } ////////////////////////////////////////////////////////////////////////////// // readConfig() // ------------ // Read configuration from settings file void QBrew::readConfig() { QSettings config; config.beginGroup(QString("/") + PACKAGE); // read general config config.beginGroup(CONFGROUP_GENERAL); state_.general.lookfeel = config.value(CONF_GEN_LOOK_FEEL, state_.general.lookfeel).toString(); state_.general.showsplash = config.value(CONF_GEN_SHOW_SPLASH, state_.general.showsplash).toBool(); state_.general.autosave = config.value(CONF_GEN_AUTOSAVE, state_.general.autosave).toBool(); state_.general.saveinterval = config.value(CONF_GEN_SAVEINTERVAL, state_.general.saveinterval).toUInt(); state_.general.autobackup = config.value(CONF_GEN_AUTOBACKUP, state_.general.autobackup).toBool(); state_.general.loadlast = config.value(CONF_GEN_LOADLAST, state_.general.loadlast).toBool(); state_.general.recentnum = config.value(CONF_GEN_RECENTNUM, state_.general.recentnum).toUInt(); config.endGroup(); // read recipe config config.beginGroup(CONFGROUP_RECIPE); state_.recipe.batch = config.value(CONF_RECIPE_BATCH, state_.recipe.batch).toDouble(); state_.recipe.style = config.value(CONF_RECIPE_STYLE, state_.recipe.style).toString(); // TODO: CONF_RECIPE_HOPFORM is deprecated (0.4.0) if (config.contains(CONF_RECIPE_HOPFORM)) { state_.recipe.hoptype = config.value(CONF_RECIPE_HOPFORM, state_.recipe.hoptype).toString(); config.remove(CONF_RECIPE_HOPFORM); } else { state_.recipe.hoptype = config.value(CONF_RECIPE_HOPTYPE, state_.recipe.hoptype).toString(); } config.endGroup(); // read calc config config.beginGroup(CONFGROUP_CALC); state_.calc.steepyield = config.value(CONF_CALC_STEEPYIELD, state_.calc.steepyield).toDouble(); state_.calc.efficiency = config.value(CONF_CALC_EFFICIENCY, state_.calc.efficiency).toDouble(); state_.calc.morey = config.value(CONF_CALC_MOREY, state_.calc.morey).toBool(); state_.calc.tinseth = config.value(CONF_CALC_TINSETH, state_.calc.tinseth).toBool(); state_.calc.units = config.value(CONF_CALC_UNITS, state_.calc.units).toString(); config.endGroup(); config.endGroup(); } ////////////////////////////////////////////////////////////////////////////// // writeConfig() // ------------- // Write configuration to settings file void QBrew::writeConfig() { QSettings config; config.beginGroup(QString("/") + PACKAGE); // write general config config.beginGroup(CONFGROUP_GENERAL); config.setValue(CONF_GEN_LOOK_FEEL, state_.general.lookfeel); config.setValue(CONF_GEN_SHOW_SPLASH, state_.general.showsplash); config.setValue(CONF_GEN_AUTOSAVE, state_.general.autosave); config.setValue(CONF_GEN_SAVEINTERVAL, (int)state_.general.saveinterval); config.setValue(CONF_GEN_AUTOBACKUP, state_.general.autobackup); config.setValue(CONF_GEN_LOADLAST, state_.general.loadlast); config.setValue(CONF_GEN_RECENTNUM, (int)state_.general.recentnum); config.endGroup(); // write recipe config config.beginGroup(CONFGROUP_RECIPE); config.setValue(CONF_RECIPE_BATCH, (double)state_.recipe.batch); config.setValue(CONF_RECIPE_STYLE, state_.recipe.style); config.setValue(CONF_RECIPE_HOPTYPE, state_.recipe.hoptype); config.endGroup(); // write calc config config.beginGroup(CONFGROUP_CALC); config.setValue(CONF_CALC_STEEPYIELD, (double)state_.calc.steepyield); config.setValue(CONF_CALC_EFFICIENCY, (double)state_.calc.efficiency); config.setValue(CONF_CALC_MOREY, state_.calc.morey); config.setValue(CONF_CALC_TINSETH, state_.calc.tinseth); config.setValue(CONF_CALC_UNITS, state_.calc.units); config.endGroup(); config.endGroup(); } ////////////////////////////////////////////////////////////////////////////// // restoreState() // ------------ // Restore application state void QBrew::restoreState() { QSettings config; config.beginGroup(QString("/") + PACKAGE); // read window state config.beginGroup(CONFGROUP_WINDOW); state_.window.statusbar = config.value(CONF_WIN_STATUSBAR, state_.window.statusbar).toBool(); QByteArray mainwindow = config.value(CONF_WIN_MAINWINDOW).toByteArray(); QMainWindow::restoreState(mainwindow); resize(config.value(CONF_WIN_SIZE, QSize(600, 400)).toSize()); config.endGroup(); // read general state config.beginGroup(CONFGROUP_GENERAL); state_.general.recentfiles = config.value(CONF_GEN_RECENTFILES).toStringList(); while (state_.general.recentfiles.count() > state_.general.recentnum) { state_.general.recentfiles.removeLast(); } config.endGroup(); config.endGroup(); // show or hide statusbar depending on initial setting statusBar()->setVisible(state_.window.statusbar); ui.actiontogglestatusbar->setChecked(state_.window.statusbar); } ////////////////////////////////////////////////////////////////////////////// // saveSate() // ---------- // Save application state void QBrew::saveState() { // load config from settings QSettings config; config.beginGroup(QString("/") + PACKAGE); // write window state config.beginGroup(CONFGROUP_WINDOW); config.setValue(CONF_WIN_MAINWINDOW, QMainWindow::saveState()); config.setValue(CONF_WIN_SIZE, size()); config.setValue(CONF_WIN_STATUSBAR, state_.window.statusbar); config.endGroup(); // write general state config.beginGroup(CONFGROUP_GENERAL); config.setValue(CONF_GEN_RECENTFILES, state_.general.recentfiles); config.endGroup(); config.endGroup(); } ////////////////////////////////////////////////////////////////////////////// // Miscellaneous // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // openFile() // ---------- // Open the named file bool QBrew::openFile(const QString &filename) { if (!filename.isEmpty()) { if (recipe_->nativeFormat(filename)) { if (recipe_->loadRecipe(filename)) { filename_ = filename; setFileCaption(filename_); } else { // load was unsuccessful QMessageBox::warning(this, TITLE, tr("Unable to load the recipe %1") .arg(QFileInfo(filename_).fileName())); return false; } } else { bool status = false; // attempt to import file if (recipe_->beerXmlFormat(filename)) { status = recipe_->importBeerXml(filename); if (status) { // set filename to the default (forces a saveas) filename_ = DEFAULT_FILE; setFileCaption(filename_); } } if (!status) { // import was unsuccessful QMessageBox::warning(this, TITLE, tr("Unable to import the file %1") .arg(QFileInfo(filename).fileName())); return false; } } newflag_ = false; backed_ = false; addRecent(filename_); ui.actionfilesave->setEnabled(false); return true; } // empty filename return false; } ////////////////////////////////////////////////////////////////////////////// // saveFile() // ---------- // Save a file bool QBrew::saveFile(const QString &filename) { bool status = true; if (!filename.isEmpty()) { status = recipe_->saveRecipe(filename); if (status) { // remove old autosave file if present if (QFile::exists(autosavename_)) { QFile::remove(autosavename_); initAutoSave(); } } } return status; } ////////////////////////////////////////////////////////////////////////////// // backupFile() // ------------ // Backup the current file bool QBrew::backupFile() { if (backed_) return true; bool status = false; if (!filename_.isEmpty() && (filename_ != tr(DEFAULT_FILE))) { if (QFile::exists(filename_ + '~')) QFile::remove(filename_ + '~'); status = QFile::copy(filename_, filename_ + '~'); if (status) backed_ = true; } return status; } ////////////////////////////////////////////////////////////////////////////// // recentMenuShow() // ---------------- // about to show recent file menu void QBrew::recentMenuShow() { ui.menuopenrecent->clear(); QAction *action; foreach(QString recentfile, state_.general.recentfiles) { action = new QAction(recentfile, ui.menuopenrecent); connect(action, SIGNAL(triggered()), this, SLOT(fileRecent())); ui.menuopenrecent->addAction(action); } } ////////////////////////////////////////////////////////////////////////////// // addRecent() // ----------- // Add a file to the recent files list void QBrew::addRecent(const QString &filename) { if (state_.general.recentnum == 0) return; QFileInfo finfo(filename); QString filepath = finfo.absoluteFilePath(); if (state_.general.recentfiles.contains(filepath)) state_.general.recentfiles.removeAll(filepath); if (state_.general.recentfiles.count() >= state_.general.recentnum) state_.general.recentfiles.removeLast(); state_.general.recentfiles.prepend(filepath); } ////////////////////////////////////////////////////////////////////////////// // autoSave() // ---------- // Time to autosave void QBrew::autoSave() { if (!recipe_->modified()) return; statusBar()->showMessage(tr("Autosaving recipe..."), 2000); if ((filename_.isEmpty()) || (filename_ == tr(DEFAULT_FILE))) { recipe_->saveRecipe(autosavename_); } else { if (state_.general.autobackup) backupFile(); recipe_->saveRecipe(filename_); } // remove "modified" from caption setFileCaption(filename_); } ////////////////////////////////////////////////////////////////////////////// // setFileCaption() // ---------------- // Set the window caption from file name void QBrew::setFileCaption(const QString &filename) { setWindowModified(false); // clear modified * QString shownname; if (filename.isEmpty()) shownname = tr(DEFAULT_FILE); else shownname = QFileInfo(filename).fileName(); setWindowTitle(QString("%1 - %2[*]").arg(TITLE.constData()).arg(shownname)); } ////////////////////////////////////////////////////////////////////////////// // querySave() // ----------- // Ask the user if they want to save their work before going on int QBrew::querySave() { return QMessageBox::question(this, TITLE + tr(" - Save?"), tr("

Do you wish to save your work first?"), QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel); } ////////////////////////////////////////////////////////////////////////////// // queryOverwrite() // ----------- // Ask the user if they want to overwrite an existing file int QBrew::queryOverwrite(const QString filename) { return QMessageBox::question(this, TITLE + tr(" - Overwrite?"), tr("

Are you sure you want to " "overwrite the file \"%1\"") .arg(filename), QMessageBox::Yes, QMessageBox::Cancel); } ////////////////////////////////////////////////////////////////////////////// // recipeModified() // ------------------ // Received when recipe is modified void QBrew::recipeModified() { ui.actionfilesave->setEnabled(true); setWindowModified(true); } ////////////////////////////////////////////////////////////////////////////// // closeEvent() // ----------- // Catch the close event void QBrew::closeEvent(QCloseEvent* e) { if (!recipe_->modified()) { e->accept(); } else { // file needs to be saved, what do we do switch (querySave()) { case QMessageBox::Yes: fileSave(); e->accept(); break; case QMessageBox::Cancel: statusBar()->showMessage(tr(READY), 2000); e->ignore(); break; case QMessageBox::No: default: e->accept(); } } } ////////////////////////////////////////////////////////////////////////////// // eventFilter() // ----------- // Filter events bool QBrew::eventFilter(QObject *obj, QEvent *event) { // necessary on Mac if (event->type() == QEvent::FileOpen) { QFileOpenEvent *openevent = static_cast(event); fileOpen(openevent->file()); return true; } else { // standard event processing return QObject::eventFilter(obj, event); } } ////////////////////////////////////////////////////////////////////////////// // dataBase() // --------- // Figure out the base directory for the data QString QBrew::dataBase() { // TODO: can QDesktopServices help us here? QString base = qApp->applicationDirPath(); #if defined(Q_WS_X11) if (QRegExp("qbrew/?$").indexIn(base) != -1) { // we have our own application directory (it ends in 'qbrew') base += "/"; } else if (QRegExp("bin/?$").indexIn(base) != -1) { // we are in the bin dir of a filesystem hiearchy like '/usr/local/bin' base += "/../share/qbrew/"; } else { // otherwise punt base += "/"; } #elif defined(Q_WS_MAC) if (QRegExp("Contents/MacOS/?$").indexIn(base) != -1) { // we're pointing inside an application bundle base += "/../Resources/"; } else { // otherwise punt base += "/"; } #else // Win32 and others base += "/"; #endif return QDir::cleanPath(base) + "/"; } ////////////////////////////////////////////////////////////////////////////// // docBase() // --------- // Figure out the base directory for the documentation QString QBrew::docBase() { QString base = qApp->applicationDirPath(); #if defined(Q_WS_X11) if (QRegExp("qbrew/?$").indexIn(base) != -1) { // we have our own application directory (it ends in 'qbrew') base += "/doc/"; } else if (QRegExp("bin/?$").indexIn(base) != -1) { // we are in the bin dir of a filesystem hiearchy like '/usr/local/bin' base += "/../share/doc/qbrew/"; } else { // otherwise punt' base += "/doc/"; } #elif defined(Q_WS_MAC) if (QRegExp("Contents/MacOS/?$").indexIn(base) != -1) { // we're pointing inside an application bundle base += "/../Resources/en.lproj/"; } else if (QFile::exists(base + "/en.lproj/")) { // some other hierarchy using Mac style l10n base += "/en.lproj/"; } else { // otherwise punt base += "/doc/"; } #else // Win32 and others base += "/doc/"; #endif return QDir::cleanPath(base) + "/"; } qbrew-0.4.1/src/hydrometertool.cpp0000644000175100017510000000620011016417220016141 0ustar daviddavid/*************************************************************************** hydrometertool.h ------------------- A hydrometer correction utility for QBrew ------------------- Copyright 2004-2008, David Johnson Based on code Copyright 2001, Abe Kabakoff Please see the header file for copyright and license information ***************************************************************************/ #include #include "resource.h" #include "data.h" #include "hydrometertool.h" using Resource::TITLE; ////////////////////////////////////////////////////////////////////////////// // HydrometerTool() // ---------------- // Constructor HydrometerTool:: HydrometerTool(QWidget* parent) : QDialog(parent) { ui.setupUi(this); QString SUFFIX = QString(" %1%2") .arg(Resource::DEGREE) .arg(Data::instance()->defaultTempUnit().symbol()); setWindowTitle(TITLE + tr(" - Hydrometer Tool")); // additional setup QFont fnt(font()); fnt.setBold(true); ui.corrected->setFont(fnt); ui.calibrated->setSuffix(SUFFIX); ui.sample->setSuffix(SUFFIX); // convert UI properties of celsius if (Data::instance()->defaultTempUnit() == Temperature::celsius) { ui.calibrated->setMinimum(0.0); ui.calibrated->setMaximum(100.0); ui.calibrated->setValue(15.0); ui.sample->setMinimum(0.0); ui.sample->setMaximum(100.0); ui.sample->setValue(15.0); } // connections connect(ui.sample, SIGNAL(valueChanged(double)), this, SLOT(recalc())); connect(ui.calibrated, SIGNAL(valueChanged(double)), this, SLOT(recalc())); connect(ui.reading, SIGNAL(valueChanged(double)), this, SLOT(recalc())); recalc(); } ////////////////////////////////////////////////////////////////////////////// // recalc() // -------- // the signal to calculate the actual SG void HydrometerTool::recalc() { // see http://www.hbd.org/brewery/library/HydromCorr0992.html const double COEFF1 = 1.313454; const double COEFF2 = 0.132674; const double COEFF3 = 2.057793E-3; const double COEFF4 = 2.627634E-6; double calterm; // E is the correction factor for the calibration temp double corr; // the corrected reading double samp = ui.sample->value(); double cal = ui.calibrated->value(); double read = ui.reading->value(); // calc needs to be in fahrenheit if (Data::instance()->defaultTempUnit() == Temperature::celsius) { samp = celsius2fahrenheit(samp); cal = celsius2fahrenheit(cal); } // Correction(@59F) = 1.313454 - 0.132674*T + 2.057793e-3*T**2 - 2.627634e-6*T**3 // the equation is set up for 59F (15C) calterm = (COEFF1 - (COEFF2 * cal) + (COEFF3 * (cal*cal)) - (COEFF4 * (cal*cal*cal))); corr = (COEFF1 - (COEFF2 * samp) + (COEFF3 * (samp*samp)) - (COEFF4 * (samp*samp*samp))) - calterm; corr /= 1000.0; corr = 1.0 - corr; corr = read / corr; // now that we have the value change the reading ui.corrected->setText(QString::number(corr, 'f', 3)); } qbrew-0.4.1/src/hopdelegate.cpp0000644000175100017510000001310111016417220015340 0ustar daviddavid/*************************************************************************** hopdelegate.cpp ------------------- Hop delegate editor ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include #include "data.h" #include "hop.h" #include "hopdelegate.h" #include "hopmodel.h" HopDelegate::HopDelegate(QObject *parent) : QItemDelegate(parent) {} HopDelegate::~HopDelegate() {} QWidget *HopDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &index) const { QComboBox *combo; QSpinBox *spin; QDoubleSpinBox *dspin; QString suffix; // can only edit name on blank row bool blank = index.row() >= index.model()->rowCount(); // different kind of editor for each column switch (index.column()) { case HopModel::NAME: combo = new QComboBox(parent); combo->setEditable(true); combo->addItem(QString()); combo->addItems(Data::instance()->hopsList()); combo->installEventFilter(const_cast(this)); return combo; case HopModel::WEIGHT: if (blank) return 0; dspin = new QDoubleSpinBox(parent); dspin->setDecimals(3); dspin->setRange(0.00, 1000.00); dspin->setSingleStep(0.25); suffix = " " + Data::instance()->defaultHopUnit().symbol(); dspin->setSuffix(suffix); dspin->setAccelerated(true); dspin->installEventFilter(const_cast(this)); return dspin; case HopModel::ALPHA: if (blank) return 0; dspin = new QDoubleSpinBox(parent); dspin->setDecimals(1); dspin->setRange(0.0, 50.0); dspin->setSingleStep(0.1); dspin->setSuffix("%"); dspin->setAccelerated(true); dspin->installEventFilter(const_cast(this)); return dspin; case HopModel::TIME: if (blank) return 0; spin = new QSpinBox(parent); spin->setRange(0, 120); spin->setSingleStep(5); spin->setSuffix(tr(" min", "minutes")); spin->setAccelerated(true); spin->installEventFilter(const_cast(this)); return spin; case HopModel::TYPE: if (blank) return 0; combo = new QComboBox(parent); combo->setEditable(true); combo->addItems(Hop::typeStringList()); combo->installEventFilter(const_cast(this)); return combo; default: return 0; } } void HopDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *combo; QSpinBox *spin; QDoubleSpinBox *dspin; int comboindex; QVariant value = index.model()->data(index, Qt::EditRole); // different kind of editor for each column switch (index.column()) { case HopModel::NAME: combo = static_cast(editor); if (!combo) return; comboindex = combo->findText(value.toString()); if (comboindex > 0) { combo->setCurrentIndex(comboindex); } else { combo->setEditText(value.toString()); } break; case HopModel::WEIGHT: case HopModel::ALPHA: dspin = static_cast(editor); if (!dspin) return; dspin->setValue(value.toDouble()); break; case HopModel::TIME: spin = static_cast(editor); if (!spin) return; spin->setValue(value.toUInt()); break; case HopModel::TYPE: combo = static_cast(editor); if (!combo) return; comboindex = combo->findText(value.toString()); if (comboindex > 0) { combo->setCurrentIndex(comboindex); } else { combo->setEditText(value.toString()); } break; default: QItemDelegate::setEditorData(editor, index); break; } } void HopDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combo; QSpinBox *spin; QDoubleSpinBox *dspin; QVariant value; // different kind of editor for each column switch (index.column()) { case HopModel::NAME: case HopModel::TYPE: combo = static_cast(editor); if (!combo) return; value = combo->currentText(); model->setData(index, value); break; case HopModel::WEIGHT: case HopModel::ALPHA: dspin = static_cast(editor); if (!dspin) return; value = dspin->value(); model->setData(index, value); break; case HopModel::TIME: spin = static_cast(editor); if (!spin) return; value = spin->value(); model->setData(index, value); break; default: QItemDelegate::setModelData(editor, model,index); break; } } void HopDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const { editor->setGeometry(option.rect); } qbrew-0.4.1/src/calcconfig.ui0000644000175100017510000001102511016417220015005 0ustar daviddavid CalcConfig 0 0 355 209 Calc Configuration Calculation Options Measurement &units units <p>Select the units of measurement you wish to use. Your choices are "<b>Metric</b>" and "<b>US</b>".</p> false Mash &efficiency efficiency <p>Enter the mash efficiency for your brew house</p> true 1.000000000000000 0.010000000000000 Steep &yield steepyield true 1.000000000000000 0.010000000000000 <p>There are two bitterness calculations that QBrew can use. The default method is to use the <b>Rager</b> formula. By selecting this check box, you can change the calculation to use the <b>Tinseth</b> formula.</p> Use &Tinseth bitterness calculation Alt+T <p>There are two color calculations that QBrew can use. The default method uses the <b>Daniels</b> formula. By selecting this check box, you can change the calculation to use the <b>Morey</b> formula.</p> Use &Morey color calculation Alt+M Qt::Vertical 337 81 units efficiency steepyield tinseth morey qbrew-0.4.1/src/grainmodel.h0000644000175100017510000000537111016417220014657 0ustar daviddavid/*************************************************************************** grainmodel.h ------------------- Grain model ------------------- Copyright 2006-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #ifndef GRAINMODEL_H #define GRAINMODEL_H #include #include #include #include "grain.h" class GrainModel : public QAbstractTableModel { Q_OBJECT public: // model columns enum { NAME, WEIGHT, EXTRACT, COLOR, TYPE, USE, COUNT }; GrainModel(QObject *parent, GrainList *list); ~GrainModel(); QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); Qt::ItemFlags flags(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; bool insertRows(int row, int count, const QModelIndex & parent = QModelIndex()); bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); void flush(); signals: void modified(); private: GrainList *list_; }; #endif // GRAINMODEL_H qbrew-0.4.1/src/miscdelegate.cpp0000644000175100017510000001153711016417220015520 0ustar daviddavid/*************************************************************************** miscdelegate.cpp ------------------- Misc delegate editor ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include #include "data.h" #include "misc.h" #include "miscdelegate.h" #include "miscmodel.h" MiscDelegate::MiscDelegate(QObject *parent) : QItemDelegate(parent) {} MiscDelegate::~MiscDelegate() {} QWidget *MiscDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &index) const { QComboBox *combo; QDoubleSpinBox *spin; QLineEdit *edit; QString suffix; // can only edit name on blank row bool blank = index.row() >= index.model()->rowCount(); // different kind of editor for each column switch (index.column()) { case MiscModel::NAME: combo = new QComboBox(parent); combo->setEditable(true); combo->addItem(QString()); combo->addItems(Data::instance()->miscList()); combo->installEventFilter(const_cast(this)); return combo; case MiscModel::QUANTITY: if (blank) return 0; spin = new QDoubleSpinBox(parent); spin->setDecimals(3); spin->setRange(0.00, 1000.00); spin->setSingleStep(0.25); suffix = " " + Data::instance()->defaultMiscUnit().symbol(); spin->setSuffix(suffix); spin->setAccelerated(true); spin->installEventFilter(const_cast(this)); return spin; case MiscModel::TYPE: combo = new QComboBox(parent); combo->setEditable(false); combo->addItems(Misc::typeStringList()); combo->installEventFilter(const_cast(this)); return combo; case MiscModel::NOTES: if (blank) return 0; edit = new QLineEdit(parent); edit->installEventFilter(const_cast(this)); return edit; default: return 0;; } } void MiscDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *combo; QDoubleSpinBox *spin; QLineEdit *edit; int comboindex; QVariant value = index.model()->data(index, Qt::EditRole); // different kind of editor for each column switch (index.column()) { case MiscModel::NAME: combo = static_cast(editor); if (!combo) return; comboindex = combo->findText(value.toString()); if (comboindex > 0) { combo->setCurrentIndex(comboindex); } else { combo->setEditText(value.toString()); } break; case MiscModel::QUANTITY: spin = static_cast(editor); if (!spin) return; spin->setValue(value.toDouble()); break; case MiscModel::NOTES: edit = static_cast(editor); if (!edit) return; edit->setText(value.toString()); break; case MiscModel::TYPE: combo = static_cast(editor); if (!combo) return; combo->setCurrentIndex(combo->findText(value.toString())); break; default: QItemDelegate::setEditorData(editor, index); break; } } void MiscDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combo; QDoubleSpinBox *spin; QLineEdit *edit; QVariant value; // different kind of editor for each column switch (index.column()) { case MiscModel::NAME: case MiscModel::TYPE: combo = static_cast(editor); if (!combo) return; value = combo->currentText(); model->setData(index, value); break; case MiscModel::NOTES: edit = static_cast(editor); if (!edit) return; value = edit->text(); model->setData(index, value); break; case MiscModel::QUANTITY: spin = static_cast(editor); if (!spin) return; value = spin->value(); model->setData(index, value); break; default: QItemDelegate::setModelData(editor, model,index); break; } } void MiscDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const { editor->setGeometry(option.rect); } qbrew-0.4.1/src/textprinter.cpp0000755000175100017510000006705311016417220015471 0ustar daviddavid/////////////////////////////////////////////////////////////////////////////// // textprinter.h // ------------------- // Copyright (c) 20072008 David Johnson // Please see the header file for copyright and license information. /////////////////////////////////////////////////////////////////////////////// #include "textprinter.h" #include #include #include #include #include #include #include #include #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 0)) #include #endif static inline double mmToInches(double mm) { return mm * 0.039370147; } /////////////////////////////////////////////////////////////////////////////// // TextPrinter() /////////////////////////////////////////////////////////////////////////////// /// Constructs a TextPrinter object. /// Any related dialogs will be displayed centered over the parent, if it is /// a QWidget object. /////////////////////////////////////////////////////////////////////////////// TextPrinter::TextPrinter(QObject *parent) : QObject(parent), parent_(0), printer_(new QPrinter(QPrinter::HighResolution)), tempdoc_(0), leftmargin_(15.0), rightmargin_(15.0), topmargin_(15.0), bottommargin_(15.0), spacing_(5.0), headersize_(0.0), headerrule_(0.5), headertext_(QString()), footersize_(0.0), footerrule_(0.5), footertext_(QString()), dateformat_() { if (parent) parent_ = qobject_cast(parent); printer_->setFullPage(true); printer_->setOrientation(QPrinter::Portrait); // for convenience, default to US_Letter for C/US/Canada // NOTE: bug in Qt, this value is not loaded by QPrintDialog switch (QLocale::system().country()) { case QLocale::AnyCountry: case QLocale::Canada: case QLocale::UnitedStates: case QLocale::UnitedStatesMinorOutlyingIslands: printer_->setPageSize(QPrinter::Letter); break; default: printer_->setPageSize(QPrinter::A4); break; } } /////////////////////////////////////////////////////////////////////////////// // ~TextPrinter() /////////////////////////////////////////////////////////////////////////////// /// Destroys the TextPrinter object. /////////////////////////////////////////////////////////////////////////////// TextPrinter::~TextPrinter() { delete printer_; } /////////////////////////////////////////////////////////////////////////////// // pageSize() /////////////////////////////////////////////////////////////////////////////// /// Return the page size. This is defined by the QPrinter::PageSize /// enumeration. /////////////////////////////////////////////////////////////////////////////// QPrinter::PageSize TextPrinter::pageSize() const { return printer_->pageSize(); } /////////////////////////////////////////////////////////////////////////////// // setPageSize() /////////////////////////////////////////////////////////////////////////////// /// Set the page size. Size is defined by the QPrinter::PageSize enumeration. /// By default, this is QPrinter::Letter in US and Canada locales, and /// QPrinter::A4 in other locales. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setPageSize(QPrinter::PageSize size) { printer_->setPageSize(size); } /////////////////////////////////////////////////////////////////////////////// // orientation() /////////////////////////////////////////////////////////////////////////////// /// Return the page orientation. This is defined by the QPrinterOrientation /// enumeration. /////////////////////////////////////////////////////////////////////////////// QPrinter::Orientation TextPrinter::orientation() const { return printer_->orientation(); } /////////////////////////////////////////////////////////////////////////////// // setOrientation() /////////////////////////////////////////////////////////////////////////////// /// Set the page orientation. Orientation is defined by the /// QPrinter::Orientation enumeration, and can be QPrinter::Portrait (the /// default) or QPrinter::Landscape. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setOrientation(QPrinter::Orientation orientation) { printer_->setOrientation(orientation); } /////////////////////////////////////////////////////////////////////////////// // leftMargin() /////////////////////////////////////////////////////////////////////////////// /// Return the left page margin width in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::leftMargin() const { return leftmargin_; } /////////////////////////////////////////////////////////////////////////////// // setLeftMargin() /////////////////////////////////////////////////////////////////////////////// /// Set the left margin width in millimeters. The default value is 15mm. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setLeftMargin(double margin) { if ((margin > 0) && (margin < printer_->paperRect().width() / 2)) { leftmargin_ = margin; } else { leftmargin_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // rightMargin() /////////////////////////////////////////////////////////////////////////////// /// Return the right page margin width in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::rightMargin() const { return rightmargin_; } /////////////////////////////////////////////////////////////////////////////// // setRightMargin() /////////////////////////////////////////////////////////////////////////////// /// Set the right margin width in millimeters. The default value is 15mm. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setRightMargin(double margin) { if ((margin > 0) && (margin < printer_->paperRect().width() / 2)) { rightmargin_ = margin; } else { rightmargin_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // topMargin() /////////////////////////////////////////////////////////////////////////////// /// Return the top page margin size in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::topMargin() const { return topmargin_; } /////////////////////////////////////////////////////////////////////////////// // setTopMargin() /////////////////////////////////////////////////////////////////////////////// /// Set the top margin size in millimeters. The default value is 15mm. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setTopMargin(double margin) { if ((margin > 0) && (margin < printer_->paperRect().height() / 4)) { topmargin_ = margin; } else { topmargin_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // bottomMargin() /////////////////////////////////////////////////////////////////////////////// /// Return the bottom page margin size in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::bottomMargin() const { return bottommargin_; } /////////////////////////////////////////////////////////////////////////////// // setBottomMargin() /////////////////////////////////////////////////////////////////////////////// /// Set the bottom margin size in millimeters. The default value is 15mm. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setBottomMargin(double margin) { if ((margin > 0) && (margin < printer_->paperRect().height() / 4)) { bottommargin_ = margin; } else { bottommargin_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // setMargins() /////////////////////////////////////////////////////////////////////////////// /// Set all margins to have the identical size. The default value is 15mm. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setMargins(double margin) { if ((margin > 0) && (margin < printer_->paperRect().height() / 2) && (margin < printer_->paperRect().width() / 2)) { leftmargin_ = rightmargin_ = topmargin_ = bottommargin_ = margin; } else { leftmargin_ = rightmargin_ = topmargin_ = bottommargin_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // spacing() /////////////////////////////////////////////////////////////////////////////// /// Return the spacing between the page content and the header and footer /// blocks. This is defined in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::spacing() const { return spacing_; } /////////////////////////////////////////////////////////////////////////////// // setSpacing() /////////////////////////////////////////////////////////////////////////////// /// Set the spacing between the page content and the header and footer blocks. /// The default value is 5mm. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setSpacing(double spacing) { if ((spacing > 0) && (spacing <= printer_->paperRect().height() / 8)) { spacing_ = spacing; } else { spacing_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // headerSize() /////////////////////////////////////////////////////////////////////////////// /// Return the height of the header block in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::headerSize() const { return headersize_; } /////////////////////////////////////////////////////////////////////////////// // setHeaderSize() /////////////////////////////////////////////////////////////////////////////// /// Set the height of the header in millimeters. The default is zero (no /// header). /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setHeaderSize(double size) { if ((size > 0) && (size <= printer_->paperRect().height() / 8)) { headersize_ = size; } else { headersize_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // headerRule() /////////////////////////////////////////////////////////////////////////////// /// Return the size of the header rule in points. One point is 1/72 inch. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::headerRule() const { return headerrule_; } /////////////////////////////////////////////////////////////////////////////// // setHeaderRule() /////////////////////////////////////////////////////////////////////////////// /// Set the header rule size in points. By default, the header rule is one half /// point (1/144 inch). To turn off the rule, set the rule size to 0. The rule /// will be drawn below the header area. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setHeaderRule(double pointsize) { headerrule_ = qMax(0.0, pointsize); } /////////////////////////////////////////////////////////////////////////////// // headerText() /////////////////////////////////////////////////////////////////////////////// /// Return the text for the header. /////////////////////////////////////////////////////////////////////////////// const QString &TextPrinter::headerText() const { return headertext_; } /////////////////////////////////////////////////////////////////////////////// // setHeaderText() /////////////////////////////////////////////////////////////////////////////// /// Set the text for the header. Rich text is supported. HTML tags may be used /// to format the text and align elements. The following page variables may be /// included in the text: /// - \&page; - Insert current page number /// - \&date; - Insert current date, using the format set with setPageFormat() /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setHeaderText(const QString &text) { headertext_ = text; } /////////////////////////////////////////////////////////////////////////////// // footerSize() /////////////////////////////////////////////////////////////////////////////// /// Return the height of the footer block in millimeters. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::footerSize() const { return footersize_; } /////////////////////////////////////////////////////////////////////////////// // setFooterSize() /////////////////////////////////////////////////////////////////////////////// /// Set the height of the footer in millimeters. The default is zero (no /// header). /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setFooterSize(double size) { if ((size > 0) && (size <= printer_->paperRect().height() / 8)) { footersize_ = size; } else { footersize_ = 0; } } /////////////////////////////////////////////////////////////////////////////// // footerRule() /////////////////////////////////////////////////////////////////////////////// /// Return the size of the footer rule in points. One point is 1/72 inch. /////////////////////////////////////////////////////////////////////////////// double TextPrinter::footerRule() const { return footerrule_; } /////////////////////////////////////////////////////////////////////////////// // setFooterRule() /////////////////////////////////////////////////////////////////////////////// /// Set the footer rule size in points. By default, the footer rule is one half /// point (1/144 inch). To turn off the rule, set the rule size to 0. The rule /// will be drawn just above the footer area. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setFooterRule(double pointsize) { footerrule_ = qMax(0.0, pointsize); } /////////////////////////////////////////////////////////////////////////////// // footerText() /////////////////////////////////////////////////////////////////////////////// /// Return the text for the footer. /////////////////////////////////////////////////////////////////////////////// const QString &TextPrinter::footerText() const { return footertext_; } /////////////////////////////////////////////////////////////////////////////// // setFooterText() /////////////////////////////////////////////////////////////////////////////// /// Set the text for the footer. Rich text is supported. HTML tags may be used /// to format the text and align elements. The following page variables may be /// included in the text: /// - \&page; - Insert current page number /// - \&date; - Insert current date, using the format set with setPageFormat() /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setFooterText(const QString &text) { footertext_ = text; } /////////////////////////////////////////////////////////////////////////////// // dateFormat() /////////////////////////////////////////////////////////////////////////////// /// Return the currently set date format string. /////////////////////////////////////////////////////////////////////////////// const QString &TextPrinter::dateFormat() const { return dateformat_; } /////////////////////////////////////////////////////////////////////////////// // setDateFormat() /////////////////////////////////////////////////////////////////////////////// /// Set the date format to be used for the \&date; page variable. The format /// is the same as that used by QDate::toString(). If no format is set, the /// format defaults to Qt::TextDate. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::setDateFormat(const QString &format) { dateformat_ = format; } /////////////////////////////////////////////////////////////////////////////// // print() /////////////////////////////////////////////////////////////////////////////// /// Print a document. A standard print dialog will be displayed. If a QPrinter /// is not provided, a temporary printer object will be used. If the caption /// parameter is not empty, it will be used as the title for the print /// dialog. /// /// \todo add overload taking html string /////////////////////////////////////////////////////////////////////////////// void TextPrinter::print(const QTextDocument *document, const QString &caption) { if (!document) return; // setup printer printer_->setOutputFormat(QPrinter::NativeFormat); printer_->setOutputFileName(QString()); // show print dialog QPrintDialog dialog(printer_, parent_); dialog.setWindowTitle(caption.isEmpty() ? "Print Document" : caption); if (dialog.exec() == QDialog::Rejected) return; // print it tempdoc_ = document->clone(); print(printer_); delete tempdoc_; tempdoc_ = 0; } /////////////////////////////////////////////////////////////////////////////// // exportPDF() /////////////////////////////////////////////////////////////////////////////// /// Export the document in PDF format. If caption is not empty, it will be used /// as the title for the dialog. If filename is empty, a standard file /// selection dialog will be displayed. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::exportPdf(const QTextDocument *document, const QString &caption, const QString &filename) { if (!document) return; // file save dialog QString dialogcaption = caption.isEmpty() ? "Export PDF" : caption; QString exportname; if (!filename.isEmpty()) { exportname = filename; } else { exportname = QFileDialog::getSaveFileName(parent_, dialogcaption, filename, "*.pdf"); } if (exportname.isEmpty()) return; if (QFileInfo(exportname).suffix().isEmpty()) exportname.append(".pdf"); // setup printer printer_->setOutputFormat(QPrinter::PdfFormat); printer_->setOutputFileName(exportname); // print it tempdoc_ = document->clone(); print(printer_); delete tempdoc_; tempdoc_ = 0; } /////////////////////////////////////////////////////////////////////////////// // preview() /////////////////////////////////////////////////////////////////////////////// /// Displays a print preview dialog. If caption is not empty, it will be used /// as the title of the dialog. /////////////////////////////////////////////////////////////////////////////// void TextPrinter::preview(const QTextDocument *document, const QString &caption) { #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 0)) if (!document) return; QPrintPreviewDialog *dialog = new QPrintPreviewDialog(printer_, parent_); dialog->setWindowTitle(caption.isEmpty() ? "Print Preview" : caption); connect(dialog, SIGNAL(paintRequested(QPrinter*)), this, SLOT(print(QPrinter*))); // preview it tempdoc_ = document->clone(); dialog->exec(); delete tempdoc_; tempdoc_ = 0; delete dialog; #endif } /////////////////////////////////////////////////////////////////////////////// // private methods /////////////////////////////////////////////////////////////////////////////// // paperRect() //////////////////////////////////////////////////////////////// // Return the size of the paper, adjusted for DPI QRectF TextPrinter::paperRect(QPaintDevice *device) { // calculate size of paper QRectF rect = printer_->paperRect(); // adjust for DPI rect.setWidth(rect.width() * device->logicalDpiX() / printer_->logicalDpiX()); rect.setHeight(rect.height() * device->logicalDpiY() / printer_->logicalDpiY()); return rect; } // contentRect() ////////////////////////////////////////////////////////////// // calculate the rect for the content block QRectF TextPrinter::contentRect(QPaintDevice *device) { // calculate size of content (paper - margins) QRectF rect = paperRect(device); rect.adjust(mmToInches(leftmargin_) * device->logicalDpiX(), mmToInches(topmargin_) * device->logicalDpiY(), -mmToInches(rightmargin_) * device->logicalDpiX(), -mmToInches(bottommargin_) * device->logicalDpiY()); // header if (headersize_ > 0) { rect.adjust(0, mmToInches(headersize_) * device->logicalDpiY(), 0, 0); rect.adjust(0, mmToInches(spacing_) * device->logicalDpiY(), 0, 0); } // footer if (footersize_ > 0) { rect.adjust(0, 0, 0, -mmToInches(footersize_) * device->logicalDpiY()); rect.adjust(0, 0, 0, -mmToInches(spacing_) * device->logicalDpiY()); } return rect; } // headerRect() ////////////////////////////////////////////////////////////// // calculate the rect for the header block QRectF TextPrinter::headerRect(QPaintDevice *device) { QRectF rect = paperRect(device); rect.adjust(mmToInches(leftmargin_) * device->logicalDpiX(), mmToInches(topmargin_) * device->logicalDpiY(), -mmToInches(rightmargin_) * device->logicalDpiX(), 0); (headerrule_ / 144.0); rect.setBottom(rect.top() + mmToInches(headersize_) * device->logicalDpiY()); return rect; }; // footerRect() /////////////////////////////////////////////////////////////// // calculate the rect for the footer block QRectF TextPrinter::footerRect(QPaintDevice *device) { QRectF rect = paperRect(device); rect.adjust(mmToInches(leftmargin_) * device->logicalDpiX(), 0, -mmToInches(rightmargin_) * device->logicalDpiX(), -mmToInches(bottommargin_) * device->logicalDpiY()); rect.setTop(rect.bottom() - mmToInches(footersize_) * device->logicalDpiY()); return rect; }; // print() //////////////////////////////////////////////////////////////////// // Common printing routine. Print tempdoc_ to given printer device. void TextPrinter::print(QPrinter *printer) { if (!printer || !tempdoc_) return; tempdoc_->setUseDesignMetrics(true); tempdoc_->documentLayout()->setPaintDevice(printer); tempdoc_->setPageSize(contentRect(printer).size()); // dump existing margin (if any) QTextFrameFormat fmt = tempdoc_->rootFrame()->frameFormat(); fmt.setMargin(0); tempdoc_->rootFrame()->setFrameFormat(fmt); // to iterate through pages we have to worry about // copies, collation, page range, and print order // get num copies int doccopies; int pagecopies; if (printer->collateCopies()) { doccopies = 1; pagecopies = printer->numCopies(); } else { doccopies = printer->numCopies(); pagecopies = 1; } // get page range int firstpage = printer->fromPage(); int lastpage = printer->toPage(); if (firstpage == 0 && lastpage == 0) { // all pages firstpage = 1; lastpage = tempdoc_->pageCount(); } // print order bool ascending = true; if (printer->pageOrder() == QPrinter::LastPageFirst) { int tmp = firstpage; firstpage = lastpage; lastpage = tmp; ascending = false; } // loop through and print pages QPainter painter(printer); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true); for (int dc=0; dcprinterState() == QPrinter::Aborted || printer->printerState() == QPrinter::Error) { return; } // print page paintPage(&painter, tempdoc_, pagenum); if (pc < pagecopies-1) printer->newPage(); } if (pagenum == lastpage) break; if (ascending) pagenum++; else pagenum--; printer->newPage(); } if (dc < doccopies-1) printer->newPage(); } } // paintPage() //////////////////////////////////////////////////////////////// // paint an individual page of the document to the painter void TextPrinter::paintPage(QPainter *painter, QTextDocument *document, int pagenum) { QRectF rect; double onepoint = painter->device()->logicalDpiY() / 72.0; // header if (headersize_ > 0) { rect = headerRect(painter->device()); if (headerrule_ > 0.0) { painter->save(); // allow space between rule and header painter->translate(0, onepoint + (headerrule_ * onepoint / 2.0)); painter->setPen(QPen(Qt::black, headerrule_ * onepoint)); painter->drawLine(rect.bottomLeft(), rect.bottomRight()); painter->restore(); } // replace page variables QString header = headertext_; header.replace("&page;", QString::number(pagenum)); if (dateformat_.isEmpty()) { header.replace("&date;", QDate::currentDate().toString()); } else { header.replace("&date;", QDate::currentDate().toString(dateformat_)); } painter->save(); painter->translate(rect.left(), rect.top()); QRectF clip(0, 0, rect.width(), rect.height()); QTextDocument doc; doc.setUseDesignMetrics(true); doc.setHtml(header); doc.documentLayout()->setPaintDevice(painter->device()); doc.setPageSize(rect.size()); // align text to bottom double newtop = clip.bottom() - doc.size().height(); clip.setHeight(doc.size().height()); painter->translate(0, newtop); doc.drawContents(painter, clip); painter->restore(); } // footer if (footersize_ > 0) { rect = footerRect(painter->device()); if (footerrule_ > 0.0) { painter->save(); // allow space between rule and footer painter->translate(0, -onepoint + (-footerrule_ * onepoint / 2.0)); painter->setPen(QPen(Qt::black, footerrule_ * onepoint)); painter->drawLine(rect.topLeft(), rect.topRight()); painter->restore(); } // replace page variables QString footer = footertext_; footer.replace("&page;", QString::number(pagenum)); if (dateformat_.isEmpty()) { footer.replace("&date;", QDate::currentDate().toString()); } else { footer.replace("&date;", QDate::currentDate().toString(dateformat_)); } painter->save(); painter->translate(rect.left(), rect.top()); QRectF clip(0, 0, rect.width(), rect.height()); QTextDocument doc; doc.setUseDesignMetrics(true); doc.setHtml(footer); doc.documentLayout()->setPaintDevice(painter->device()); doc.setPageSize(rect.size()); doc.drawContents(painter, clip); painter->restore(); } // content painter->save(); rect = contentRect(painter->device()); painter->translate(rect.left(), rect.top() - (pagenum-1) * rect.height()); QRectF clip(0, (pagenum-1) * rect.height(), rect.width(), rect.height()); document->drawContents(painter, clip); painter->restore(); } qbrew-0.4.1/src/graindelegate.cpp0000644000175100017510000001246211016417220015663 0ustar daviddavid/*************************************************************************** graindelegate.cpp ------------------- Grain delegate editor ------------------- Copyright 2006-2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include #include #include #include "data.h" #include "grain.h" #include "graindelegate.h" #include "grainmodel.h" GrainDelegate::GrainDelegate(QObject *parent) : QItemDelegate(parent) {} GrainDelegate::~GrainDelegate() {} QWidget *GrainDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &index) const { QComboBox *combo; QDoubleSpinBox *spin; QString suffix; // can only edit name on blank row if (index.row() >= index.model()->rowCount()) return 0; // different kind of editor for each column switch (index.column()) { case GrainModel::NAME: combo = new QComboBox(parent); combo->setEditable(true); combo->addItem(QString()); combo->addItems(Data::instance()->grainsList()); combo->installEventFilter(const_cast(this)); return combo; case GrainModel::WEIGHT: spin = new QDoubleSpinBox(parent); spin->setDecimals(3); spin->setRange(0.00, 1000.00); spin->setSingleStep(0.25); spin->setAccelerated(true); suffix = " " + Data::instance()->defaultGrainUnit().symbol(); spin->setSuffix(suffix); spin->installEventFilter(const_cast(this)); return spin; case GrainModel::EXTRACT: spin = new QDoubleSpinBox(parent); spin->setDecimals(3); spin->setRange(1.000, 1.100); spin->setSingleStep(0.001); spin->setAccelerated(true); spin->installEventFilter(const_cast(this)); return spin; case GrainModel::COLOR: spin = new QDoubleSpinBox(parent); spin->setDecimals(1); spin->setRange(0.0, 500.0); spin->setSingleStep(1.0); spin->setSuffix(Resource::DEGREE); spin->setAccelerated(true); spin->installEventFilter(const_cast(this)); return spin; case GrainModel::TYPE: combo = new QComboBox(parent); combo->setEditable(false); combo->addItems(Grain::typeStringList()); combo->installEventFilter(const_cast(this)); return combo; case GrainModel::USE: combo = new QComboBox(parent); combo->setEditable(false); combo->addItems(Grain::useStringList()); combo->installEventFilter(const_cast(this)); return combo; default: return 0; } } void GrainDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QComboBox *combo; QDoubleSpinBox *spin; int comboindex; QVariant value = index.model()->data(index, Qt::EditRole); // different kind of editor for each column switch (index.column()) { case GrainModel::NAME: combo = static_cast(editor); if (!combo) return; comboindex = combo->findText(value.toString()); if (comboindex > 0) { combo->setCurrentIndex(comboindex); } else { combo->setEditText(value.toString()); } break; case GrainModel::WEIGHT: case GrainModel::EXTRACT: case GrainModel::COLOR: spin = static_cast(editor); if (!spin) return; spin->setValue(value.toDouble()); break; case GrainModel::TYPE: case GrainModel::USE: combo = static_cast(editor); if (!combo) return; combo->setCurrentIndex(combo->findText(value.toString())); break; default: QItemDelegate::setEditorData(editor, index); break; } } void GrainDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QComboBox *combo; QDoubleSpinBox *spin; QVariant value; // different kind of editor for each column switch (index.column()) { case GrainModel::NAME: case GrainModel::TYPE: case GrainModel::USE: combo = static_cast(editor); if (!combo) return; value = combo->currentText(); model->setData(index, value); break; case GrainModel::WEIGHT: case GrainModel::EXTRACT: case GrainModel::COLOR: spin = static_cast(editor); if (!spin) return; value = spin->value(); model->setData(index, value); break; default: QItemDelegate::setModelData(editor, model,index); break; } } void GrainDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const { if (editor) editor->setGeometry(option.rect); } qbrew-0.4.1/src/datareader.cpp0000644000175100017510000002320311016417220015157 0ustar daviddavid/*************************************************************************** datareader.cpp ------------------- XML stream reader for QBrew data ------------------- Copyright 2008, David Johnson Please see the header file for copyright and license information ***************************************************************************/ #include "data.h" #include "resource.h" #include "datareader.h" using namespace Resource; // DataReader /////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // DataReader() // ------------ // Constructor DataReader::DataReader(QIODevice *device) : QXmlStreamReader(device) { } ///////////////////////////////////////////////////////////////////////////// // readData() // ---------- // Read in data bool DataReader::readData(Data *data) { QString buf; bool grainsflag=false, hopsflag=false, miscsflag=false; bool stylesflag=false, utilflag=false; data->grainmap_.clear(); data->hopmap_.clear(); data->miscmap_.clear(); data->stylemap_.clear(); data->utable_.clear(); // parse file while (!atEnd()) { readNext(); if (isStartElement()) { // qbrewdata tag if (name() == tagDoc) { // check version buf = attributes().value(attrVersion).toString(); if (buf < DATA_PREVIOUS) { // too old of a version qWarning() << "Error: Unsupported data version"; raiseError("Unsupported data version"); return false; } } // styles else if (name() == tagStyles) { stylesflag = true; } // style else if (name() == tagStyle) { if (!stylesflag) qWarning("Warning: mislocated style tag"); Style style; buf = attributes().value(attrOGLow).toString(); style.setOGLow(buf.toDouble()); buf = attributes().value(attrOGHigh).toString(); style.setOGHi(buf.toDouble()); buf = attributes().value(attrFGLow).toString(); style.setFGLow(buf.toDouble()); buf = attributes().value(attrFGHigh).toString(); style.setFGHi(buf.toDouble()); buf = attributes().value(attrIBULow).toString(); style.setIBULow(buf.toInt()); buf = attributes().value(attrIBUHigh).toString(); style.setIBUHi(buf.toInt()); buf = attributes().value(attrSRMLow).toString(); style.setSRMLow(buf.toInt()); buf = attributes().value(attrSRMHigh).toString(); style.setSRMHi(buf.toInt()); style.setName(readElementText()); data->insertStyle(style); } // grains else if (name() == tagGrains) { grainsflag = true; } // grain else if (name() == tagGrain) { if (!grainsflag) qWarning("Warning: mislocated grain tag"); Grain grain; grain.setWeight(Weight(1.0, data->defaultGrainUnit())); buf = attributes().value(attrExtract).toString(); grain.setExtract(buf.toDouble()); buf = attributes().value(attrColor).toString(); grain.setColor(buf.toDouble()); // TODO: grain type deprecates data file format (0.4.0) grain.setType(attributes().value(attrType).toString()); grain.setUse(attributes().value(attrUse).toString()); grain.setName(readElementText()); data->insertGrain(grain); } // hops else if (name() == tagHops) { hopsflag = true; } // hop else if (name() == tagHop) { // TODO: attrForm is deprecated 0.4.0 if (!hopsflag) qWarning("Warning: mislocated hop tag"); Hop hop; buf = attributes().value(attrQuantity).toString(); hop.setWeight(Weight(1.0, data->defaultHopUnit())); buf = attributes().value(attrAlpha).toString(); hop.setAlpha(buf.toDouble()); hop.setName(readElementText()); data->insertHop(hop); } // miscs else if (name() == tagMiscs) { miscsflag = true; } // misc else if (name() == tagMisc) { if (!miscsflag) qWarning("Warning: mislocated misc tag"); Misc misc; misc.setQuantity(Quantity(1.0, data->defaultMiscUnit())); misc.setType(attributes().value(attrType).toString()); misc.setNotes(attributes().value(attrNotes).toString()); misc.setName(readElementText()); data->insertMisc(misc); } // utilization else if (name() == tagUtilization) { utilflag = true; } // entry else if (name() == tagEntry) { if (!utilflag) qWarning("Warning: mislocated entry tag"); UEntry entry; buf = attributes().value(attrTime).toString(); entry.time = buf.toUInt(); buf = attributes().value(attrUtil).toString(); entry.utilization = buf.toUInt(); data->addUEntry(entry); } // unknown tag, skip else { qWarning() << "Warning: Unknown tag" << name().toString(); skipElement(); } } else if (isEndElement()) { // unset flags if (name() == tagStyles) stylesflag = false; else if (name() == tagGrains) grainsflag = false; else if (name() == tagHops) hopsflag = false; else if (name() == tagMiscs) miscsflag = false; else if (name() == tagUtilization) utilflag = false; } } return true; } // skip over xml element void DataReader::skipElement() { if (isStartElement()) { int level = 0; while (!atEnd()) { readNext(); if (isStartElement()) level++; if (isEndElement()) level--; if (level < 0) break; } } } // DataWriter ///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // DataWriter() // ------------ // Constructor DataWriter::DataWriter(QIODevice *device) : QXmlStreamWriter(device) { #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 0)) setAutoFormatting(true); setAutoFormattingIndent(2); #endif } /////////////////////////////////////////////////////////////////////////////// // writeData() // ----------- // Write out data bool DataWriter::writeData(Data *data) { writeStartDocument(); writeDTD(QString("").arg(tagDoc)); // write root element writeStartElement(tagDoc); writeAttribute(attrVersion, VERSION); // write styles writeStartElement(tagStyles); foreach(Style style, data->stylemap_) { // iterate through grain list writeStartElement(tagStyle); writeAttribute(attrOGLow, QString::number(style.OGLow())); writeAttribute(attrOGHigh, QString::number(style.OGHi())); writeAttribute(attrFGLow, QString::number(style.FGLow())); writeAttribute(attrFGHigh, QString::number(style.FGHi())); writeAttribute(attrIBULow, QString::number(style.IBULow())); writeAttribute(attrIBUHigh, QString::number(style.IBUHi())); writeAttribute(attrSRMLow, QString::number(style.SRMLow())); writeAttribute(attrSRMHigh, QString::number(style.SRMHi())); writeCharacters(style.name()); writeEndElement(); } writeEndElement(); // tagStyles // write grains writeStartElement(tagGrains); foreach(Grain grain, data->grainmap_) { // iterate through grain list writeStartElement(tagGrain); writeAttribute(attrExtract, QString::number(grain.extract())); writeAttribute(attrColor, QString::number(grain.color())); writeAttribute(attrType, grain.type()); writeAttribute(attrUse, grain.use()); writeCharacters(grain.name()); writeEndElement(); } writeEndElement(); // tagGrains // write hops writeStartElement(tagHops); foreach(Hop hop, data->hopmap_) { // iterate through hop list writeStartElement(tagHop); writeAttribute(attrAlpha, QString::number(hop.alpha())); writeCharacters(hop.name()); writeEndElement(); } writeEndElement(); // tagHops // write misc ingredients writeStartElement(tagMiscs); foreach(Misc misc, data->miscmap_) { // iterate through misc list writeStartElement(tagMisc); writeAttribute(attrType, misc.type()); writeAttribute(attrNotes, misc.notes()); writeCharacters(misc.name()); writeEndElement(); } writeEndElement(); // tagMiscs // write utilization table writeStartElement(tagUtilization); foreach(UEntry uentry, data->utable_) { // iterate through entries writeStartElement(tagEntry); writeAttribute(attrTime, QString::number(uentry.time)); writeAttribute(attrUtil, QString::number(uentry.utilization)); writeEndElement(); } writeEndElement(); // tagMiscs writeEndElement(); // tagDoc writeEndDocument(); return true; } qbrew-0.4.1/src/databasetool.ui0000644000175100017510000000431611016417220015364 0ustar daviddavid DatabaseTool 0 0 600 400 Database Tool 9 6 0 128 0 0 600 29 &File :/pics/icons/filesave.png Save Save the database Ctrl+S :/pics/icons/exit.png Quit Close the database tool Ctrl+Q qbrew-0.4.1/docs/0000755000175100017510000000000011016417327012530 5ustar daviddavidqbrew-0.4.1/docs/primer/0000755000175100017510000000000011016417334014024 5ustar daviddavidqbrew-0.4.1/docs/primer/primer.html0000644000175100017510000000263011016417220016203 0ustar daviddavid The QBrew Brewing Primer Previous Contents Next


The QBrew Brewing Primer

David Johnson david@usermode.org


What good's a brewing calculator if you don't know how to brew? Not much. So if you're wanting to know how to brew your own, keep browsing...

Your First Brew

The Mashing Primer

The Design Primer

Brewing Equipment

Some Recipes to Play With

A Brewing Glossary


©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/primer/primer-glossary.html0000644000175100017510000002366411016417220020056 0ustar daviddavid The QBrew Brewing Primer: Brewing Glossary Previous Contents Next

A Brewing Glossary


Airlock
A device that lets CO2 escape from the fermentor without letting outside air or contaminants in.
Alpha Acid
The primary bittering agent in hops. Also known as humulone. Hops are rated by their percentage of alpha acid. The abbreviation for alpha acid is "AA".
Aroma Hops
Hops noted for their aroma instead of their bitterness. They are low in alpha acids, the resins that cause the bitterness in beer. These are usually added towards the end of the boil, or used for dry-hopping. Examples include Cascade, Hallertau and Tettnang.
Beer Kit
The simplest way to make beer. Consists of a can of malt extract that is pre-hopped with a pack of dry yeast under the lid. The usual procedure with these kits is to pour the can into a plastic bucket with water, add one or two cans of table sugar, and spinkle the yeast on top. This rarely results in a great beer.
Blow-Off Tube
Made of one-inch inside diameter vinyl tubing, one end of a blow-off tube is stuck tightly into the neck of a carboy with the other end stuck in a large jar of water. They are used when fermenting in five gallon carboys so that the foam, or krausen, developed during fermentation is expelled into the jar. When fermentation has slowed down and the krausen subsides, it should be replaced with an airlock.
Burton Salts
A mixture of salts and minerals designed to mimic the chemistry of Burton-on-Trent, the birthplace of pale ale. A teaspoon per five gallons is plenty to make soft or mild water reasonably similar to the water from the brewing region.
Conversion
The process of changing starch into sugar during the mash. Enyzymes in the mash break down the complex starch molecules into simpler sugars.
Decoction
A method of mashing, traditionally used with lagers and under-modified malts. The mash is started at a lower temperature, then a portion of the mash is removed, boiled, and added back to the mash. This raises the mash to the next temperature needed. Decoction mashing gives a beer a maltiness not achievable with any other method.
Ester
A chemical compound formed through the oxidation of certain alcohols. The aromas of esters are fruity, and the smells we associate with apples, bananas, etc., are in fact esters. A beer with an abundance of fruity flavor and aroma is an estery beer. Ales tend to be estery. Beers fermented at too high of a temperature can produce more esters than are desired.
Fermentation
When yeast consume sugar, they produce alcohol and carbon dioxide. This process is known as fermentation.
Grist
The total amount of malt and grains used in a recipe. Also known as a grain-bill.
Hydrate
The opposite of dehydrate. Dry yeast is just that, dry, and must be reconstituted with water before using for best results. Boil a cup of water, let it cool to a lukewarm temperature, sprinkle the dry yeast on top, and cover with foil. It will be ready to pitch in twenty minutes.
Hydrometer
A device used to measure the specific gravity of a liquid. A hydrometer can tell a brewer how much residual sugar is in his or her wort, thus predicting the strength of the finished product.
IBU
A measurement of bitterness in beer. Stands for Internation Bittering Units.
Krausen
The layer of foam created on top of fermenting beer. Also refers to a portion of fermenting beer added to finished beer in order to cause carbonation.
Light-Struck
Beer will develop a skunky or catty taste when exposed to light, especially sunlight. For this reason, beer should be put in brown glass bottles. If you want to know what lightstruck beer tastes like, pick up a green glass bottle of Heineken or Grolsch that has been sitting under flourescent lights in the store.
Lovibond
A measurement of color, particularly in brewing ingredients. Using lovibond, you can calculate your beer's color from its ingredients. Lovibond is measured in degrees, and "17L" would be read as 17 degrees lovibond, which is also the color of Michelob Dark! The lovibond scale tops off at 40L, the color of black stout.
Malt
Germinated grain. Regular grain kernels are not suitable for mashing by themselves. Malted grains have been soaked in water, allowed to germinate, or start growing, and then dried and cured. This process creates the enzymes responsible for converting starch to sugar, and makes the starch in the grain accessible to those enzymes. Different malting and curing procedures create the wide variety of malts vailable.
Mashing
A process of making beer from scratch. Instead of using malt extract to make wort, mashing makes wort from the basic malt grains. The grains are crushed, placed in an insulated container with pre-heated water, and left to sit for an hour or so at about 150 degrees. This converts the starch in the grains to fermentable sugars. Then the mash is slowly rinsed to extract the sugars. Once mashing is complete, the rest of the brewing procedure is the same.
Original Gravity
See Specific Gravity.
Pitch
The process of adding yeast to wort to start fermentation. Yeast is not thrown in the fermentor with an overhand throw like in baseball, but rather poured in by hand.
Priming
The process of adding sugar to finished beer for bottling. The remaining yeast in the beer ferment this sugar to cause carbonation.
Racking
A fancy term for siphoning or transfering beer from one container to another.
Smack Pack
A form of liquid yeast. They consist of a pouch of liquid yeast with a smaller pouch of starter wort inside. When the smack pack is "smacked", the inner pouch ruptures and the yeast begin growing in the starter. The pack will expand to about two inches thick in a few days, ready for pitching.
Sparge
The process of rinsing sugars from the grain bed after the mash. Sparging is also called lautering. Sparging is typically done in a lauter tun, which holds the grain above a false bottom perferated with tiny holes. This allows the liquid to gently flow through the grains. A common ailment in sparging is the stuck-sparge. This results when the grain bed is clogged with sediment or gummy substances, clogging the flow.
Specific Gravity
A measurement of density, where water has a density of 1.000. Dissolved sugars will increase the density of water. Thus, specific gravity will measure the amount of fermentable sugar present in wort. A gravity measurement taken before fermentation starts is known as original gravity, while a measurement taken after fermentation is known as terminal gravity, or final gravity. Worts with a gravity over 1.075 are known as high-gravity worts.
Starter
A tiny portion of wort used to grow the yeast to pitching proportions. A few hundred yeast cells taken from a slant would be overwhelmed by five gallons of beer, so they are given a couple of ounces to chew on first, then a quart, and finally, when they are numerous enough, added to the carboy of wort. Smack-packs have their own starter wort included, and they can be pitched directly to the carboy when ready.
Steeping
The process of soaking grains in hot water to extract their flavor. This is different than mashing, as no conversion takes place. Steeping is often used by extract brewers to add a multitude of malt flavors to their wort without having to mash.
Torrified Grain
Adjunct grains that have been processed to make them suitable for mashing. They resemble "puffed" breakfast cereals, such as puffed wheat, rice puffs, etc.
Wort
Unfermented, or raw, beer. In Jamaica they make a non-alcoholic drink called malta that is essentially wort.

©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/primer/primer-mashing.html0000644000175100017510000001573511016417220017641 0ustar daviddavid The QBrew Brewing Primer: The Mashing Primer Previous Contents Next

The Mashing Primer


This primer assumes you are familiar with the basic procedures of extract brewing. If you are not, brew at least one beer using the extract method.

Mashing is the process of creating malt extract or wort from whole malt grains. This is the way all brewers brewed until about 100 years ago. It is a little more time consuming, but the results are well worth it. With mashing you get the freshest possible wort and the widest variety of beer styles available to you.

The mashing method used here is known as infusion mashing. It is the method most used ales and stouts. Because it is the simplest method, it is also a favorite among homebrewers. Other mashing methods are the step mash, the decoction mash, and the weird and rarely used turbid mash.


The Recipe

This recipe is for an all grain version of a basic pale ale. As always, you can change the hops and yeast around to suit your taste, but for right now keep the malts the same.

  • 10 lbs. Pale Ale Malt
  • 1 lb. 50-60L Crystal Malt
  • 1 tbl. Black Patent
  • 1 oz. Northern Brewer (1 hour)
  • 1/2 oz. Northern Brewer (20 minutes)
  • 1/2 oz. Northern Brewer (2 minutes)
  • 1 smack-pack English-style Ale Yeast
  • 3/4 cup Priming Sugar

The black malt may seem strange in a pale ale, but it is there for a reason. Black and roasted malts are very acidic, and your mash needs to be slightly acidic (5.2 to 5.4 pH) in order for it to work efficiently. This bit of black malt will help your mash pH, but the amount is so small that it won't affect the flavor or color of the finished beer.


Crush Your Grains

Crushing your grains properly is critical for a good mash. There is no substitute for seeing first hand what properly crushed malt looks like. Many mail order supply houses will ship you your grain pre-crushed, for only ten to twenty cents more a pound.

The kernel of the grains should be crushed into grits, but not so fine as to be flour. The husks of the grains should be whole. They will act as a filtering agent in the mash. If you do use a grain without a husk, such as wheat or rye, make sure you are also using plenty of barley malt, or add rice or oat hulls to your mash.


Heat Your Strike Water

Measure out one quart of water for every pound of grain being mashed. For this recipe, this will be eleven quarts (2.75 gallons). Heat this water to 170 degrees Fahrenheit, in a five gallon or greater brewpot.

If you are going to be using a combination mash/lauter tun, pour a half gallon of near-boiling water into it, and cover. This will pre-heat the tun. Dump this water out just before adding the grains during the next step.


Mashing In

If you have a combination mash/lauter tun, pour the heated water into the tun, otherwise keep it in the brewpot. Slowly pour in the crushed grains, stirring at the same time. All the kernels should be thoroughly wet. Make sure that there aren't any lumps. Take the temperature of the mash. It should be 155 degrees. If it isn't, adjust with boiling or cold water. It helps to have a teapot ready on the stove. If you're off a couple of degrees, don't worry about it.

If using a combination tun, cover it up. If using a brewpot to mash in, or an uninsulated mash tun, put the lid on, and cover and wrap with plenty of towels to act as insulation.

At this temperature, the enzymes will convert the starch in the malt to sugars and dextrins. At the beginning, the mash will look like porridge, but as conversion takes place, the liquid in the mash will clear, making the mash look more like a thick grain broth.


The Mash

Let the mash sit for one full hour. The temperature will slowly drop over this time. Every twenty minutes, take a temperature reading. If it has dropped to 150 degrees or less, add a little bit of boiling water and stir. If you are using a brewpot to mash in, sticking it into a preheated 155 degree oven is a great way to eliminate worry. Unfortunately, most ovens don't have this low of a setting.

While the mash is working, prepare five gallons of 180 degree water, and one gallon of boiling water.


Mashing Out

After sixty minutes is up, transfer the mash to your lauter tun, if you aren't already using a combination tun. When transferring, don't just pour it in. Gently add the mash with a ladle. Avoid splashing or vigorous stirring.

Add the one gallon of boiling water, and gently stir. Cover the lauter tun while you set up your sparging equipment.

Heating the mash with this water will raise its temperature up enough so that the sugars will flow easily from the grains.


Sparging

You should have some sort of apparatus that will slowly transfer the five gallons of 180 degree water to the lauter tun. This usually consists of a hot liquor tank, tubing, and a sparge arm to sprinkle the water on the mash. Set this up now.

Open the spigot on the lauter tun, slowly drawing off a quart of extract. This will be cloudy. Gently pour it back into the tun. Keep doing this until the runoff clears. This process helps set the filter bed in the mash. You shouldn't have to take more than a gallon or two to do this.

Start the sparge water flowing from the hot liquor tank to the lauter tun and into the brewpot. This should be a slow stream, timed so that the entire sparging process takes 45 to 60 minutes. Sparging gently washes the sugars out of the mash. Collect six and a half gallons of wort in your brewpot.


Afterword

Your mash is now complete. You are now ready for the boil. The rest of the brewing process is the same as for extract brewing, with one exception.

The wort needs to be boiled for a total of 90 minutes. The mashed wort is high in proteins, and the extra thirty minutes of boil allows the protein to coagulate and precipitate out, eliminating a lot of haze in the finished beer. After thirty minutes of boiling, you can add your first hop additions.


©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/primer/primer-equipment.html0000644000175100017510000007344311016417220020222 0ustar daviddavid The QBrew Brewing Primer: Brewing Equipment Previous Contents Next

Brewing Equipment


Basic Equipment

Fermenters

A fermenter is the single most important piece of equipment a brewer owns. Second-hand brewpots and bottle cappers can be used to make first quality beer, but a scratched, leaky, or flimsy fermenter will guarantee a spoiled product. So always get the best quality fermenter you can. I don't recommend plastic fermenters at all. Not in the least. If you want to know about plastic fermenters, see the section on junk.

Glass carboys, or water bottles, are by far and away the best fermenters for the homebrewer. Some commercial brewers even use them! Glass is easy to clean, since you can always see the spots you missed, and there are no corners to evade a brush. They are scratch resistant, and don't harbor the odors of long lost brews.

Carboys come in a variety of sizes. The small two and a half gallon size is good for half batches, or for meads, where you don't neccesarily want to make a full five gallons. The standard five gallon size is great for secondary fermenters, but for primary fermentations, use a six and a half or seven gallon carboy if you can. When you put in five gallons of wort into a seven gallon carboy, you have plenty of headspace. If you use a five gallon model, you must either use a blow-off tube or reduce your wort to only four gallons, topping it off with a gallon of water when you bottle or rack to the secondary.

Another alternative to fermenters is to use five gallon soda kegs. With a little work, an airlock can fit on the "in" line. Like a five gallon carboy, make sure you have plenty of headspace. One advantage to using soda kegs is that you can use CO2 pressure to transfer you beer instead of starting a siphon.

I've seen fermenters made out of old copper milks cans and other dairy equipment. If you have access to them, great.

Brewpots

A brewpot can be a five gallon stainless steel pot, an eight gallon canning pot, or anything else that will hold the wort and sit on a burner. It is best to boil your full quantity of wort, so the ideal size of brewpot would be eight to ten gallons, for a five gallon batch. If you boil in a smaller size, your wort will be concentrated, resulting in poor hop utilization. Plus, you have to do a lot of topping off in the fermenter.

Brewpots should be as rugged as you can find them. Lids and handles are a necessity. If you can find one with a spigot installed at the bottom, great. Ones with thermometer probes are even better. Enameled pots should be free of chips. Copper brewpots are the best if you can afford, or even find, them. Stainless steel is very durable, can be scrubbed with brute force, and resist dents. However, stainless steel doesn't conduct heat very well, so your wort is prone to scorch in spots. By all means, avoid aluminum, as it reacts with the acidic wort, causing strange and unpalatable flavors in your finished beer.

They should be able to fit on your stove top, even if they have to fit over two burners. Gas stoves are preferable to electric. If you can't use your brewpot on a stove, see the additional equipment section for outdoor burners.

I have seen plastic brewpots! They have an electric burner built and insulated from the plastic. By all accounts, they work well, and are relatively inexpensive. On the down side, boiling wort produces deposits on the walls of the brewpot, called beer stone, that takes scrubbing or oven cleaner to remove. Plastic can't hold up to this kind of cleaning.

Cleaning Stuff

Of course, you equipment has to be clean. Old fashioned elbow grease, sponges and nylon scrub pads will do most of the work for you. But cleaning in places where your hands won't reach requires specialized tools. Bottle brushes and carboy brushes are a necessity. Even if you have a jet-type bottle washer, brushes are still needed to do the scrubbing. There is even a tiny bottle brush made for cleaning out airlocks.

Cleaning may get the dirt off, but you need a sanitizer to get the microbes off. The two most common sanitizers for homebrewing are chlorine bleach and iodine sanitizer. Campden tablets are also used, but more suited towards wine and mead making and I would advise against using them. If you decide to use chlorine bleach, it must be unscented. Iodine sanitizer is sold under many different brand names. B.T.F. and Iodophor being common brands.

If you use chlorine bleach, add one to two ounces to five gallons of cold water. Sanitize your small equipment in this solution for at least a half of an hour. Then pour your solution into your carboy to sanitize it. Rinse off the solution from your equipment with clean, sanitary water.

If you use iodine sanitizer, mix two ounces into five gallons of water. Let all equipment remain in contact with the solution for at least five minutes. With this type of sanitizer, you can let your equipment air-dry without rinsing.

I can not stress enough the importance of sanitizing. Don't kill yourself over it, but if you neglect it, your beer will let you know by way of strange odors, foul tastes and exploding bottles.

Siphoning Stuff

Beer is susceptible to oxygen, so pouring fermented beer from the primary to secondary fermenter, or into bottles is not allowed, as the splashing will cause oxydation and stale flavors. Siphoning will need to used instead, unless you have a setup with lots of spigots.

5/16 inch inside diameter vinyl tubing is the normal siphon tubing used by homebrewers, although larger diameter tubing allows for quicker siphoning. Make sure the tubing is food grade. The tubing you find in the tropical fish section of your local pet store isn't always food grade. You don't want vinyl flavors in your finished product. The tubing is next to impossible to clean on the inside, so keep spares around if you find out a bit of mold took a liking to the tubing. A piece about three to five feet long should be plenty.

Siphon tubing doesn't stay straight inside a carboy, so a racking cane is needed. It looks like a shepards crook, with a removable tip on the end. The straight end goes all the way down to the bottom of the transfer vessel, the crooked end connects to the siphon tubing, and the other end of the tubing goes to the receptor vessel. Make sure your tubing fits your racking cane.

Racking canes can be acrylic plastic, stainless steel, or copper. With acrylic, you can see if there are any mold spots on the inside, but they are fragile, and tend to shatter just when you get a siphon started. You can't inspect steel or copper canes for wayward fungi or microbes, but they can be put in dishwashers, or have boiling water poured through them.

If your brewpot, bottling bucket, and fermentor have spigots at the bottom, you can avoid siphoning altogether, but make sure your spigots don't cause a lot of aeration. Ball valves are preferable to other kinds.

Bottling Stuff

Even though you don't want to use a plastic bucket to ferment in, they are great for bottling buckets. Just be extra careful in cleaning and sanitizing it. Siphon your finished beer into the bucket, add your bottling sugar, and gently stir, being careful not to aerate your beer. Buckets with spigots at the bottom are great time savers, since you don't have to start a siphon. If you bucket doesn't have a spigot, you can get them cheaply, and they're easy to install.

A bottle filler allows you to fill your bottles with a minimum of fuss. Plastic fillers are the most common, but the brass varieties will last a lifetime. Avoid fillers with lots of little parts and springs, as they tend to wear out and stick, and are hard to clean. Stick them on the end of your siphon hose. When you press down on the filler, beer flows, when you lift up, the flow stops. Much easier than using a clamp or valve.

Bottle cappers come in a bewildering variety of shapes, sizes and forms. Wing cappers are hand held: simply place on top of bottle and press down on the levers. These are the cheapest, most common, and they don't take up a lot of room. Bench cappers take up a lot more room, but are easier to use, especially if you are capping a lot of bottles, all the same size. Just put a bottle on the stand, pull down lever, done. If you use bottles of different sizes, you have to keep readjusting the bench capper height. If you perchance find a hammer operated capper at a flea mart, leave it there!

Your bottles should be brown glass, and non-threaded. Avoid clear or green bottles, as they will promote light-struck beer. Bottles with Grolsch type swing tops work well, just make sure the rubber gaskets are good. The easiest way to get your bottles is to buy them with the beer already in them! Drink them up, clean off the lables with plenty of elbow-grease, and use. Most homebrew supply stores will carry cases of empty, new 12 and 22 oz. bottles.

There are two basic variety of bottle caps, the first being the plain cap, the second being the oxygen-absorbing cap. This type of cap is good for barleywines and other beer needing a lot of aging, since these caps will absorb any oxygen out of the headspace. Since they are activated by water, place what you need in a cup of sanitized water right when you are ready to bottle. Don't use the very inexpensive caps you can find at the soda distributors. They are not as strong a cap, and we used them on one batch, only to have them bulge and break the seal.

Miscellaneous Stuff

You will need an airlock of some kind, with a stopper to fit your fermenter. Airlocks come in two basic varieties, the three piece and the "S" type. The three piece is much easier to clean, but they both do their job well. If you can find the pyrex lab quality ones, go for it.

A good thermometer is essential. It should have a range from 70 to 212 degrees, and be suitable for sticking in boiling liquids. Most candy thermometers will work, but their range tends to be too high.

Other equipment you will need are muslin or nylon bags to hold crushed grains or hops, a long handled brewing spoon (not wood), an postage style scale to measure hops, and various kitchen utensils such as funnels, strainers, teaspoons, etc.


Additional Equipment

Grain Mills

In order to get the most out of your grains, they must be properly crushed. A grain mill makes this much, much easier than the rolling pin method, especially if you're crushing twelve pounds of grain for a mash. Many homebrew shops will crush the grain for you, but for maximum freshness, they should be crushed the day of brewing.

Corona type mills are the least expensive and most common. They have a tendency to grind more than crush. This is okay for steeping, but for mashing, a proper crush is tantamount to a good yield.

Roller type mills crush malt the best. They may be made with single or double rollers, and some can be adjusted while cranking, and others not. The ease of cranking can be a major factor in selecting a mill, especially if you're crushing a lot of grain. If you can, try out a mill before you buy. You should expect to pay about 80 to 120 dollars for a good grain mill.

Wort Chillers

A good wort chiller can be one of the best investments made in homebrewing. It is essential that your finished wort be cooled to pitching temperatures as quickly as possible. Setting your brewpot out in the snow bank takes too much time. Besides the risk of infection, long cooling times can cause greater amounts of DMS to form (which causes vegetable like flavors).

Immersion wort chillers are simply coils of copper or steel tubing that is set inside the brewpot. Water running through the chiller cools off the wort. They are very easy to sanitize, just put the cleaned coil in the brewpot ten minutes before the end of the boil, and the heat sterilizes it. Most immersion chillers have fitting to fit a garden hose, so if you brew inside, you will need a kitchen faucet adaptor, or have to snake your hose in through the kitchen window. Chillers tend to come in 25 or 50 foot lengths, for five and ten gallon batches, respectively.

Counterflow wort chillers operate by flowing the hot wort through a tubing that is surrounded by a larger tubing with cold water running in the opposite direction (hence the name). These chillers can cool wort as fast as it can flow from one end to the other. Usually, the wort is siphoned from the brewpot, through the chiller, and directly into the fermenter. Some even have valves to inject oxygen for aeration enroute. Counterflow chillers are more expensive then immersion chillers, but you can buy the fitting seperately and make your own with a length of garden hose and copper tubing. One drawback to counterflow chillers is that they are difficult to clean and sanitize, and some brewers resort to running boiling water through them.

Hydrometers

Just about every book on basic homebrewing lists hydrometers as mandatory equipment. They are not mandatory, but they are usefull in two areas: knowing your beer's strength so you can place it in the proper judging category, and for batch logging to record what you've done so you can repeat it again.

Hydrometers look like thermometers, but they measure specific gravity, or how much your beer weighs, instead. Water has a specific gravity of 1.000, with wort having gravities of 1.020 to 1.150 or higher. As a point of comparison, wort for a pale ale tends to fall between 1.035 to 1.050 gravities. Hydrometers are calibrated to read at a temperature of 60 degrees fahrenheit.

Outdoor Cookers

Some people, especially spouses, parents, children and roommates, object to the smell of brewing beer. I am baffled as to why. It's no worse than the smell of canning vegetables. But if you and your hobby have been banished from the house, then you need an outdoor cooker. These cookers operate on propane, and put out enough heat to bring your brewpot to a rolling boil. They are sometimes known as Cajun cookers.

Your cooker should have a minimum of a 30,000 BTU heat output. The higher outputs will bring your wort to a boil quicker, but the lower rated ones are great for the fine control needed for mashing. Try to get one with a ring shaped burner. The single-jet burner types tend to scorch as a lot of heat is applied to one small spot on the kettle. If you can't get five gallons of wort to boil with one of these, you may be using a gas-grill type regulator. Switch it out to regulator that came with the cooker and you'll be fine.

Yeast Culturing Stuff

Culturing your own yeast is a great way to get just the right yeast for your beer. Yeast can be bought in slants, or cultured from the bottles of bottle-conditioned commercial beer. There's a lot of stuff you can use to culture your own yeast, so only the basics will be listed here.

Canning jars and lids are great for making up enough starters to last all year. Just make up a batch of starter twelves times a big as normal, and can it in twelve jars just like you're canning jelly. If you don't can your starters, you'll need a small pot or erlenmeyer flask to boil up your individual starters. If you prefer to use airlocks with your starters, make up a lid with a hole in it the right size to hold an airlock and stopper. Make sure everything is sanitized! If you are only going to use smack-packs and vials, this is all you need.

If you will be growing yeast from slants or bottles, a nichrome culturing loop, alcohol lamp and a test tube are essential. Yeast nutrient is can be added to plain wort to grow the yeast in, or you can buy wort specially made for growing yeast. Petri dishes, extra test tubes or blank slants, and agar medium are necessary if you'll be keeping yeast cultures for extended periods of time, or if you want to purify your culture.


Mashing Gear

Mash Tuns

For the most part, your brewpot can double as your mash tun. It holds your grains and liquor (water) at the precise temperature needed to convert starch into sugar, and to break down proteins. If you want a separate mash tun, a five gallon stainless steel pot with a lid works great, but you might need something larger if you are mashing high gravity brews. Picnic and water coolers also work great. You can also get a combined mash-lauter tun. This is discussed below.

A mash tun should retain its temperature well. Wrap flexible insulation around the tun, or rest the tun in a preformed box of styrofoam. You can also set the tun on the stove, monitor the temperature, and give it short bursts of heat to maintain the temperature.

Lauter Tuns

A lauter tun is used to hold the mashed grains while the wort is sparged. Like a mash tun, it should be insulated. A false bottom or manifold in the lauter tun hold the grain back while the sweet wort is drawn off from the bottom. There are many different varieties of lauter tuns. Picnic cooler style tuns have a manifold of slotted copper pipe in the bottom. Gott cooler types have plastic false bottoms that are perferated with myriad tiny holes. Lauter tuns made from kegs or large kettles also have false bottoms, but they are made of stainless steel or copper. Some have circular slotted copper manifolds. Thousands of years ago, lauter tuns were wooden troughs with a bed of clean straw or twigs to act as a false bottom.

Many homebrewers prefer to combine their mash and lauter tuns into one unit. This is simply done by just using the lauter tun to mash in. If you do this, make sure your tun is very well insulated, or made of metal so you can apply direct heat to it. The reason why very few commercial brewers combine their tuns is because while they use the lauter tun for one batch, they can be cleaning the mash tun, and mashing in it while the sparge is still taking place. Commercial brewers have their time and labor scheduled for the utmost efficiency, while we homebrewers can do what we want.

Sparging Stuff

A sparger is a unit that gently and gradually adds hot water to the lauter tun. Most spray the water on top of the grain bed, while others drip the water, and still others gently pour the water in through a tiny tube. The sparger should let the water flow slowly enough to let the entire process last for forty-five minutes.

A hot liquor tank holds the hot water needed for sparging. A simple bottling bucket will do for five gallon batches, but you'll need to insulate it, and install a spigot if it doesn't have one already. Some brewers use their mash tun as a hot liquor tank. Don't use your brewpot, because that's what you'll be sparging into.

Of course, you'll need tubing to go from the the hot liquor tank to the sparger, and from the lauter tun to the bottom of the brewpot. Don't let the wort splash into the brewpot or you'll get hot-side aeration, which is not a good thing.


Kegging Gear

Kegs

Commercial style beer kegs are too hard to use for homebrewers. If someone gives you a beer keg because they've heard you make your own beer, thank them profusely, and use it to make a mash tun instead.

What homebrewer's use to keg their beer are soda kegs. They come in a variety of sizes, but the five gallon is the most common. Used soda kegs are becoming more common as time goes by since many soda distributors are switching to syrup-in-a-box systems. Used kegs run about forty dollars, but check around. I've seen them as low as twenty dollars. Sometimes you can get them for free if you know where and when to look.

When you get your keg, make sure it is free of any soda smells. You do not want your porter tasting like Cherry Coke! Replace all the o-rings and poppet valves if they haven't been already. Its a good idea to replace them once a year anyway. TSP (trisodium phosphate) is the best cleaner to use on kegs, but wear gloves. Do not use bleach sanitizers with stainless steel kegs. Use iodine solutions instead, or (carefully) pour five gallons of boiling water in them to sanitize.

Kegs will have either pin-lock or ball-lock fittings. Either one is fine, but if you get more than one keg, make sure they're all the same.

CO2 Tanks

To dispense your beer from kegs, you will need to use a CO2 tank. These can be pricy to own, which is why the small five pound model is popular. However, the five pound model is also the most difficult to find. CO2 tanks have to be recertified every so often in most places.

Along with your tank, you need to get a CO2 pressure regulator. Most regulators have a range from zero to 100 PSI, which is much more than you need. The best kind to get have a pressure range of zero to fifty PSI. A "gauge cage" is a steel cage that clamps around your regulator to protect it if your tank falls over.

Fittings, Hoses and Spigots

To run the CO2 from the tank to the keg, you need a gas connector. To run the beer from the keg to your glass, you need a tap. Both of these will have fittings for the keg. The fitting for the inlet will not fit on the outlet, and vice versa. There is a huge variety of fittings, hoses, rings, manifolds, etc. available for kegging. Instead of trying to decide what you need, just get a kegging kit.

Counterpressure Bottle Fillers

Kegging is convenient and easy, but sometimes you need beer in a bottle, for a competition for instance, or for delivery to a friend. In these cases, a counterpressure bottle filler lets you bottle beer from the keg.

There are many different C.P. bottle fillers out there, but they all work on the same principle. Fit the filler into the bottle, equalize the pressure, ease off the pressure to let the beer flow into the bottle, stop the flow, cap. You must practice, practice, practice this sequence on plain water before you try it out for the first time on your favorite batch. Once you get the rythm down, its easy.

Keg Alternatives

Kegging and kegging equipment can be expensive. Even if money is no object, sometimes you want your beer in the fridge, and your keg won't fit. The most popular keg alternative is the mini-keg. These are five-liter systems that originated in Germany. These tiny kegs are reusable, and their beer is dispensed with either CO2 cartridges, or a hand operated pump. A mini-keg will last about ten uses, if you care for them properly.

The Party Pig® is also a favorite. It is a two gallon acrylic plastic jug with a tap, that will fit into a fridge. Instead of using CO2 or a hand pump to dispense the beer, a special pack is used. This is fitted into the pig before racking the beer inside. When activated, the pack swells, pressurizing the beer.


Time Saving Widgets

These items are in no way necessary to brew beer, but they have been invented to save the time of the brewer, who usually has little to spare. These are great to add to your christmas or birthday lists.

Bottle Trees and Carboy Stands

When a bottle or carboy has been sanitized, it needs to drain before use. Setting them upside down in your dishwasher is the usual method, but sometimes the dishwasher is in use, or you don't trust its sanitation.

Bottle trees are poles with little prongs sticking out to hold bottles upside down to drain. They screw apart so that the pieces of the tree can be sanitized. And since all your fifty-plus bottles are in one spot, bottling go by that much quicker. An alternative to the bottle tree is the bottle rack, which is a board with holes in it to hold the bottles by the neck.

A carboy stand holds your carboy upside-down while drying. For ten to twenty dollars, they tend to be pricey for what you do with them, but it's better than holding them upside-down in your hands for fifteen minutes! Plus, it's easy to make one yourself.

Carboy Handles and Straps

Full carboys are very heavy objects. And if you've managed to spill some wort on the outside of them, they're slippery too. Carboy handles and straps help you to manage the load safely. Handles fit around the neck of the carboy, but I don't trust them to hold the entire weight of the carboy, and recommend them only to steady and balance the thing. They are several kinds of straps, but the nicest one I've seen is actually a nylon carboy bag with strap-handles, that does double duty as a carboy insulator.

Siphon Starters

Since the days of Archimedes, the intrepid inventer has been searching for new and different ways to start a siphon. The popularity of homebrewing has only hastened that search, and a menagerie of siphon starters in all sizes and shapes can be had. Some are more complicated than they are worth. Others work only some of the time. But some are simply fantastic time-savers. My advice is to ask your homebrew supplier on his or her opinion. If you can, try them out before you by.

Bottle Washers

After you've cleaned out your bottles with soap and water, do rinse the soap out, rinse again, then again, and yet again? You definately do not want sopay tasting beer. A way to speed this process up twenty fold is to get a bottle washer. These items fit onto your kitchen faucet. When you push a bottle onto them, a spray of high pressure water quickly rinses them out. They're also great for dislodging the stubborn yeast that has dried on the bottom of your used homebrew bottles. Some models come with a double jet system, that makes your work go by twice as fast. If you bottle your beer, they are certainly worth looking into.


Junk to Avoid

Not everything that you find in a homebrew supply store is worth your money.

Plastic Fermenters

While I won't argue to merits of closed versus open fermentation, it is agreed by all that plastic fermenters are unsuitable for either. They may work for one batch, but sooner or later you will have to scrub them with a gallon of elbow grease, and then you'll have scratches. Scratches are the perfect place for bacteria to hide in to avoid your sanitizing solution. Glass carboys, though more expensive, will last a lifetime, and are a cinch to clean.

Unfortunately, most prepackaged homebrewing kits include a plastic bucket fermenter, to save money. In my opinion, when they cheapen the kit, they cheapen the hobby! Use the fermenter as a storage container for all your stuff, and buy a carboy instead.

That Winemaking Stuff Next to the Brewing Stuff

There is a lot of equipment meant for making wine that's jumbled up on the shelves with the beer making stuff. Sometimes they are even labeled as suitable for homebrewing. Don't be fooled. Titration test kits are useless in brewing beer. Corking systems are great for bottling magnums, but unless your hobby spills over into meads, don't even bother with corks or anything associated with them. Most wine sanitizers are meant to work with the acidity of wine, and do a poor job with beer. And all those vials and packets of strange chemicals...ignore them.

Okay, there's one exception: if you want to use real wooden kegs, wine making supplies are often the only source for the stuff you need to care for them. But know what you're doing first. Wooden kegs are not for the faint of heart. In the days before stainless steel, there were people who did nothing else but clean and care for wooden kegs.


All the Rest

Refrigerators

If you keg your beer, or want to make a lager, a refrigerator is almost a necessity. And even if you don't keg or lager, an extra one is nice to store your aging beer or yeast cultures. Used refrigerators tend to run about $100 in this part of the world. Shop around and you might find one for about fifty. But check them out. They're being sold for some reason, make sure it's not because they won't keep cold!

If you do get a brewing fridge, a brewing thermostat is definately the next item to get. They will keep your refrigerator at the exact temperature you need, and they'll save you energy too.

Filters

For those who want to make crystal clear beer just like the big boys do, a beer filter will take you there. Homebrew filters are placed between two kegs. CO2 pressure is used to transfer beer from one keg, through the filter, and into the other keg.

Use finings before you filter. Don't throw your money away by clogging your filters with yeasts and proteins that could have been removed with a teaspoon of isinglass. Think of filters as the final step in perfecting your beer, not as whitewash to hide your mistakes. Crystal clear beer is very recent development in the history of beer, and many beer styles allow a certain amount of haze and cloudiness in the glass to account for it.


©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/primer/primer-firstbrew.html0000644000175100017510000003535611016417220020223 0ustar daviddavid The QBrew Brewing Primer: Your First Brew Previous Contents Next

Your First Homebrew


Making your own beer is easy. People have been brewing at home for several thousand years, and they did it with much less education and resources than you now possess.

The beer we are going to make is known as an extract beer. It is slightly more complex than beer kits, where you just add water, but it will have a much more refined taste. Plus, it's much easier than mashing your beer the old-fashioned way, and with the quality of ingredients now available, it's hard to tell the difference.

You may discover simpler how-to guides than this, and that's fine. I've decided to err on the side of quality rather than simplicity.


Necessary Equipment

Beginning homebrew kits can be bought from homebrew supply shops for about $40 to $60, but they rarely include all the essential equipment. They are still a good deal, however, because they're cheaper as a package, rather than buying everthing seperate. If your kit doesn't contain all of the items listed below, make sure you pick them up as well. Some items can be found in your kitchen, and others you can borrow from a fellow brewer.

  • Carboy, five or six and 1/2 gallon, preferably glass
  • Brewpot, five to ten gallon, the bigger the better, with a lid
  • Brushes, both carboy and bottle varieties
  • Airlock and stopper, three piece locks are easier to clean
  • Racking cane, with tip
  • Siphon tubing, to fit the racking cane
  • Bottling bucket, five or seven gallon plastic bucket
  • Bottle capper
  • Bottle filler
  • Bottles and caps, 54 twelve ounce brown glass bottles with crown caps
  • Cheesecloth bag, to hold one pound of grain
  • Brewing spoon, long enough to stir the brewpot with
  • Thermometer, candy-type, with a range from 100 to 200 degrees
  • Miscellaneous kitchen utensils, funnels, strainers, teaspoons, etc.
  • Sanitizer, chlorine or iodine based
  • Blow-Off tube, used if you only have a five gallon carboy.

The Recipe

This is a basic recipe for an American style ale. I've left some decisions up to you, so it might end up as a pale ale, and amber ale, or something else. If your beginner's brewing kit includes ingredients (as many do), by all means use it! Just check to see that all the ingredients listed below are included in some variation or another. Above all, get the freshest fixings available.

Malt Extract
Six pounds of either light or amber unhopped malt extract syrup. If all you can use is hopped extract, because that's what came with your brewing kit, just make sure you use an aroma hop in this recipe. Your homebrew supplier can help you.
Specialty Grains
One pound of crystal malt grains. Crystal malts are rated by color, so get a light (10L) to medium (55L) color malt. You can get them crushed or uncrushed, but uncrushed grains stays fresher longer, something to think about if you're ordering through the mail.
Hops
Two ounces of a pacific northwest hop, such as Centennial, Columbus or Galena. They may be in either pellet, plug or whole leaf form. Pellets are easier to use, as there are no hop petals to interfer with your siphon, and they tend to keep better. On the other hand, you can't beat fresh whole hops for flavor.
Yeast
One smack pack of British or American style ale yeast. There are a gadzillion varieties of yeasts out there, but as long as it's not a lager, wheat or exotic yeast, it will work fine.
Bottling Sugar
Three-fourths cup of brewer's bottling sugar (corn sugar). Do not mistake this for Karo syrup! If you must, you can substitute an equal amount of brown sugar.

Brewing

Preparation

Three to four days before the big brew day, you need to "smack" your yeast. Read the directions on the smack pack for how to do this. Make sure you shaike it vigorously for several minutes afterwards. Put the smack pack in a cool place, like your pantry, until brew day. By then, the pack will have swollen to about two inches thick. If it hasn't, it probably wasn't fresh enough, so postpone brew day an extra couple of days to give it more time.

On brew day, organize your brewing area. Make sure your equipment is clean. Sanitize your carboy, racking cane, airlock, stopper and siphon tubing. Re-read these directions so that you'll know what to do next at any given time.

Steeping

If you haven't bought your specialty grains pre-crushed, you'll need to crush them now. Place them on a cutting board, and use a rolling pin to crush them to grit sized pieces. They should not be ground or chopped. Put the crushed grains into your cheesecloth bag.

Set the brewpot on the stove and add four to five gallons of non-chlorinated water, but not so much that it would be apt to boil over. Turn up the heat and put in the bag of grains. Monitor the temperature and move the bag often to prevent scorching. When the temperature is 170 degrees, turn off the heat, cover the pot, and let it sit for ten to twenty minutes. (now is a good time to put your malt syrup in a sink of hot water so it will flow easily during the next step). Pick up the bag, let it drain, then pour two or three cups of hot water over it slowly to rise out all the good stuff.

Boiling

Bring the brewpot up to a boil and stir in your malt extract. Be sure to stir the bottom of the pot so the extract won't burn. This addition of extract will lower the temperature, so bring it back up to a full rolling boil. Note the time and add one ounce of hops. Keep a close eye on the mixture, because as every experienced brewer can tell you, "an unwatched pot always boils over!"

Forty minutes later, add one-half ounce of hops. Fifteen minutes later add the remaining half ounce of hops.

One hour after you have started the boil (five minutes after the last hops were added), turn off the heat and cover the pot. This is now a critical time for beer. From now on, let nothing that has not been sanitized come in contact with the beer until you drink it.

Chilling

The 212 degree unfermented beer, called wort, needs to be cooled down to about 70 degrees as fast as possible. Eventually you will purchase a wort-chiller and wonder how you ever lived without one. Until then, here's what to do.

Stick your pot in the sink, or if it doesn't fit, into your bathtub. Fill the sink with water halfway up the pot (more thatn this and you risk the pot tilting over). Open the drain slightly and allow water to run into the sink so that there's a slight flow of cold water running around the brewpot. Add plenty of ice around the pot. When it all melts away, add another quantity of ice. When it melts, the pot should be cool enough. Feel the sides of the pot to make sure. If the temperature only down down to 80 degrees or so, that's fine.

Racking

The process of siphoning beer from one container to another is called racking. You now need to rack your beer from the brewpot into a sanitized carboy.

Set the brewpot on a counter with the carboy on the floor below it. Assemble the sanitized racking cane and siphon hose, and fill with clean, sanitary water. Pinch off the end of the hose, stick the end of the racking can into the brewpot all the way to the bottom, and begin siphoning. Don't worry about the dregs left behind in the brewpot when you're done.

If you don't feel like siphoning yet, use a large funnel, and pour the wort into the carboy, but be carefull, it's heavy. It's also a good idea when pouring to use a strainer to strain out whole hops if you used them.

You probably won't have a full five gallons of wort, since a lot of the water evaporated in the boil. Use clean, sanitary water to fill the carboy to the five gallon level.


Fermentation

"Pitch" the Yeast into the carboy by cutting off a corner of the smack pack with scissors and pouring the lot into the wort. If you used dry yeast, it will have to be hydrated first.

Cover the carboy with a piece of sanitized foil and rock it back and forth, splashing the wort around. This is called aerating the wort. Do your rocking on a piece of carpet or cardboard so that the carboy won't chip or break. Keep it up for five minutes at least. It's good exercise.

If you use a five gallon carboy, you will need to use a blow-off tube. If you use a six and a half gallon carboy, assemble your airlock and stopper and fit into the carboy. Make sure that you still have plenty of space between the top of the wort and the neck of the carboy. Fermenting beer will develop a lot of foam, and it's not a pretty site to wake up in the morning to find a third of your beer on the floor. Put your beer in a cool, dark corner or cellar, cover with a towel to keep stray light out, and wait one week. You can watch the airlock while you wait.

If after a week, you still get bubbles through the airlock more often than once every thirty seconds, wait some more until it has slowed down.


Bottling

It is a good idea to have someone help you bottle. There are a lot of steps in bottling, so it might help to practice first. Develop a good rhythm. A good division of labor is for the brewer to fill the bottles and the helper to cap them.

Clean and sanitize your bottles, caps, siphoning apparatus, bottle filler and bottling bucket. Boil a sauce pan of water with the 3/4 cup of bottling sugar. The sugar solution will give the yeast in the bottles just enough stuff to ferment as to cause carbonation. Cover and let it cool. Put the sugar solution in the bucket and siphon the beer from the carboy into the bucket. Try not to agitate or spash the beer. Gently stir the beer a couple of times to make sure the sugar is well distributed.

Assemble your racking cane and siphon tubing, this time adding the bottle filler to the end of the tubing. The bottle filler will let the beer flow when you gently push down on the tip, and stop the flow when you lift up.

Siphon the beer from the bottling bucket into an empty bottle, cap the bottle, get another empty bottle, repeat.

Put the bottles in a cool dark place, not your fridge, because the yeast still have a little work left to do. The beer will be ready to drink in two weeks (one week if you're desperate). Uncap, pour into your favorite beer glass, leaving the thin layer of yeast behind. Enjoy! It's the best beer you've ever had!


Appendix

Sanitation

Sanitation does not mean sterilization. Sterilizing your equipment is simply not practical for a home brewer. Sanitizing will get rid of almost all the beer-spoiling microbes on your equipment. Your goal is to make sure your friendly yeast get to your beer before other microscopic beasties can.

Nothing can be sanitized if it is not cleaned first. Bits of grease, grime and scum are perfect places for bacteria to hide from your sanitizer. Clean your countertops. Clean your hands too! Keep flies away from everything.

The three most common sanitizers for homebrewing are campden tablets, chlorine bleach, and iodine sanitizer. Campden tablets are more suited towards wine and mead making and I would advise against using them. If you decide to use chlorine bleach, it must be unscented. Iodine sanitizer is sold under many different brand names. B.T.F. and Iodophor being common brands.

If you use chlorine bleach, add one to two ounces to five gallons of cold water. Sanitize your small equipment in this solution for at least a half of an hour. Then pour your solution into your carboy to sanitize it. Rinse off the solution from your equipment with clean, sanitary water.

If you use iodine sanitizer, mix two ounces into five gallons of water. Let all equipment remain in contact with the solution for at least five minutes. With this type of sanitizer, you can let your equipment air-dry without rinsing.

Siphoning

Starting a siphon is easy, just suck on the end of the tube. But don't do it! You get all of the bacteria in your mouth on the siphon tube where they will find their way into your beer. Starting a sanitary siphon is not much more difficult, however. There are a few siphon-starting gadgets on the market, but they have their drawbacks.

The most basic way to start a siphon is to fill the racking cane and tubing with clean, sanitary water. If you have a bacterial filter on your kitchen faucet, just hold the end of the tube under a stream of water. Otherwise, use one of those tiny funnels that will fit into the tubing, and slowly pour in some bottled water. When the water reaches the end of the racking cane, pinch off the end of the siphon tube. Make sure that there aren't any air bubbles in the line. Put the tip on the racking cane.

Make sure that the bottom of the container you are siphoning from is higher than the top of the container you are siphoning to. Put the end of the racking tube in the first container all the way to the bottom. Put the end of the siphon tubing into the other container, and voila! A siphon.

It is a very good idea to practice this technique with buckets of plain water before you attempt it for the first time on your precious beer.


©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/primer/primer-design.html0000644000175100017510000003523311016417220017457 0ustar daviddavid The QBrew Brewing Primer: The Design Primer Previous Contents Next

The Design Primer

The design of beer, like much of human creativity, is a mix of science and art. An architect must know the science of structure, as well as the art of form. One without the other leads to uselessness or ugliness. For this reason, the Design Primer is divided into two sections: the science of beer design, and the art of beer design.


The Science of Beer Design The Art of Beer Design

The scientific approach to beer design starts with an analysis. Once a beer style has been chosen, examine and analyze its numbers: International Bittering Units, Original Gravity, Terminal Gravity, Color. Read the style description to determine what attributes are mandatory, acceptable or forbidden.

We will choose an Irish dry stout as an example for beer design. The numbers tell us that dry stouts have an OG of 1038 to 1048, bitterness of 30 to 40 IBU's, and a color of over 40 SRM. The style description tells us that dry-roastiness and head character are mandatory, a woody-earthy flavor is acceptable, along with some lactic sourness, and strong hop flavors and aroma are forbidden.

Next, we build up our recipe by ingredients, making sure the numbers true to form. Malts are the best place to start, followed by hops, yeast and water.

Use pale malts to build up the gravity. An exception to this is where styles demand base malts other than pale, such as viennas and bocks. In all cases, start with the lightest malt acceptable for the style. Extract brewers should start with pale malt extracts. Adjust the amount of these malts to bring the predicted original gravity to the lower or middle of the acceptable range.

Six pounds of pale malt extract, or seven and a half pounds of pale ale malt grains will produce an original gravity of about 1.042.

Build up the color and character of the recipe with specialty grains. In this there is no substitute for knowing your ingredients. A half pound of chocolate malt will add the same amount of color as four pounds of 50L crystal malt, but the resulting characters will be vastly different. As a rule of thumb, use the middle color range of malts (chocolate malt instead of crystal or black malt). Again, check your style descriptions. Check the resulting color against the style. Also recheck the original gravity calculations.

The style calls for roasted barley. Since this is a very dark grain, we won't use any other specialty grain for color. However, the medium body and caramel flavor for dry stouts means we'll have to use crystal malts to build up the wort. A pound of light (20L) crystal, and a pound and a half of roasted barley will give us a color of 52 SRM (160 home color units). The added grains will bring the original gravity up to 1.052. We might want to lower the base malts down a bit to adjust this number.

Now we need to work on the hops. Always choose a hop variety to reflect the style origin. Use British hops for British styles, German hops for German styles, and so on. When a style calls for hop bitterness, flavor and aroma to be balanced, add the hops at different times during the boil, rather than all at once. Compute the hop bitterness for each hop addition and adjust until the numbers are right.

For a dry stout, hop flavor and aroma is negligible, so we'll ignore it, and stick with a higher alpha hop, since a lesser amount will be needed, and the flavor and aroma will be driven off in the boil. We'll choose Northern Brewer, since it has a British origin, but has a much higher alpha acid content than the traditional East Kent Goldings. This particular example package of hops has an alpha acid percentage of 8%. Using our calculator, we find that one and a half ounces of hops added to the start of the boil will yield us 36 IBU's. Just right.

You can be a bit more subjective in the selection of yeasts. A yeast with a neutral character is always safe. If there is a yeast traditionally used for your intended style, then use it. For some styles this is mandatory. For other styles, different yeasts can fill out the character of the finished beer. Read the yeast descriptions if you can find them. As with hops, matching the origin of the yeast to the style is a good idea.

We note that several yeast brands carry an "Irish Ale" or an "Irish Dry Stout" yeast. We will use these. If they weren't available, we would consider another British yeast, preferably one that imparts an earthy character.

Finally we come to the water. The true scientific brewer would use various salts and additives to make the brewing water match the style's water of origin. For many brewers, this is not practical or desirable. Water chemistry can be safely ignored, unless the style says otherwise. An exception to this would be if your brewing water was excessively hard or soft to begin with, or had unusual amounts ions in it. In these cases, correct the water to normal levels.

For a dry stout, there is no particular style guideline for water. If we wished to make our water match that of Dublin's, we certainly could. Of course, we should make sure our water is not extremely hard or soft.

Some styles call for ingredients other than malt, hops, yeast or water. There are simply too many adjuncts to include in this primer. Instead, find out what other brewers are using, and how. Read the winning recipes in "Zymurgy". Examine the recipes in Recipe Lists. Try to find at least three good recipes that use your desired ingredient, and compare them.

A lactic sourness is mentioned in the description of dry stouts. Many brewers use a bit of lactic acid to achieve this. Others sour a portion of their beer. However, since this is a not a requirement of the style, we will not attempt to add that character to our beer.

One must also consider brewing technique. Many lager styles demand double or triple decoctions. Simulating a lambic would require a demanding fermentation schedule. If your beer style requires a brewing technique out of the ordinary, it would behoove you to use it. Sometimes the ingredients will demand special procedures. Very pale malt often requires a protein rest during the mash.

Finally, we are at the end of the scientific design method. We have skipped some topics of this approach, but they are useless to us until we have learned about the art of designing beer.

As the old saying goes, "If you want to play the blues, you have to pay your dues". This is applicable to every art form. If you paint, you must know your pigments. If you play guitar, you must know your chords. It is the same way with beer design. You must know your ingredients, styles, and equipment. And you must practice, practice, practice. Practice brewing other folk's recipes. Practice brewing an old favorite but with different hops. Practice with other methods of brewing. In practicing, you learn the fundamentals of the art.

We will choose a Pale Ale as an example for beer design. It is assumed that you have studied the scientific method of beer design first. You have to know the rules intimately before you are qualified to break them. Thus, you should have a basic, bare-bones pale ale recipe already ready.

Taste as many different beers that fit your intended style as is practical. You can't design a masterpiece of a stout if the only stout you've ever tasted has been Guinness. What makes the different samples different from each other? What do they have in common?

From these tastings, and from you experience as a brewer, what do characteristics do you want your beer to have? Maltiness? Bitterness? A hint of coffee? Are you able to provide your beer with these qualities? If you want the clean, crisp bitterness of a Bohemian Pilsener, but don't have the capability to lager, then you should try something else.

Go to your local brewpub and taste their pale ale. Take notes. Go to the liquor store and pick up a few commercial examples. Taste them. Take notes. For this example, we decide that we want the following characteristics: sharp bitterness, woody aroma, caramel notes, and a hint of red in the color.

As with the scientific method, start with the malt. This is the canvas upon which you will paint. Do you use pale or pilsener malts? Extra light malt extract or amber extract? From your experience, you should know which malts grant which quality. Your vision of what your beer will be might point you towards complicated mashing schedules or towards simple extracts.

Malt extracts will tend to have thin body as compared to all-grain recipes. Pale malts produce a "bigger" body than lager malts. Infusion mashes will have less maltiness than decoction mashes. Dark malts have a curious flavor-to-color continuum that requires understanding through practice. Crystal and caramel malts have yet a different flavor-to-color continuum.

We will start with a canvas of light malt extract. This will have a neutral base from which we can work. For the caramel notes we definitely need crystal or caramel malt. Since the darker crystal malts add reddish notes to the color, we will choose a scottish crystal malt of 90L color. Our experience as brewers will tell us how much to use. We will also add a tablespoon of chocolate malt to add complexity.

Hops are the bold colors in our palette. High-alpha hops are great for adding bitterness, but they are not always desirable. But on the other hand, too much low-alpha hops used for bitterness can lend a "spinachy" quality. Blends of hops can add wonderful complexity to beers. Many hop additions at narrow time intervals will extract more flavor and aroma than just a couple. Refer to your tasting notes.

Pale ales are noted for the bitterness, so we have to paint broad strokes. We will start with galena hops for their high-alpha content. For the woody notes, fuggles are ideal (and traditional). We end up with galena additions at the start of the boil, fuggles at twenty minutes, galena at fifteen minutes (to get some of its noted flavor), fuggles at ten, fuggles at five, galena and fuggles at two, and dry hopping with fuggles. Whew! The exact proportions we determine through experience.

There is no substitute for experience in choosing a yeast. You may already have chosen a "house" yeast, one that you use for every batch. Many award winning breweries do this. In painting, the quality of the pigments is of great concern. So it is with all brewing ingredients, but especially yeast. Check the time stamps on your smack packs, or culture your own. Dry yeast are okay for lesser beers, but for your masterpiece they won't do at all unless they are absolutely fresh. Using more than one kind of yeast is perfectly normal. Do it if your vision tells you to.

We loved the character that the "Irish Dry Stout" gave to our scientifically brewed stout, and decide to use it for the pale ale! It gave us a woody, earthy character. This yeast will be our drama.

For water, the scientific method can serve us well. But a good rule of thumb is to use the water one already has, without adjustments, just like a master painter uses his or her natural talents. If the water is not perfectly suitable, one can either take note of it and let it be, or adjust the recipe to compensate. Perhaps a sauer mash or dark malts can be used to adjust pH. Or perhaps blending one's hard water with quality bottled soft water.

Our water, in this example, is of medium hardness. Traditionally, pale ales were brewed with hard water. Our artistic sense tells us not to use prepackaged "Burton" salts. Of course, if we had to work with soft water, we might have to. We decide to use our water as is, with pride.

Other ingredients can be the hallmark of the artistic brewer. But experience, not experimentation is needed. For fruit beers, the finished fruit flavor will not always taste like the fresh fruit, since many aromas will have been purged with the CO2 during fermentation. For your masterpiece, it goes without saying that real fruit is preferable to bottled extracts. Honey can add a lightness to the beer, but be aware that it will ferment slowly.

We will not use any other ingredients in our beer. However, if it turns out superb, we might decide to use it as the basis for a future fruit beer. An artistic brewer is always thinking about future brews. Let's see, blackberry pale ale. Apricot pale ale. Cranberry pale ale. Hey, that last one sounds interesting!

If your finished beer is not a masterpiece, don't fret. Taste and analyse the results to help determine what needs adjusting. A bit more aroma hops? A bit less vienna malt? Pass it out to all your friends and drink the rest up to give you room for your next creation.


©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/primer/primer-recipes.html0000644000175100017510000003045011016417220017634 0ustar daviddavid The QBrew Brewing Primer: Some Recipes to Play With Previous Contents Next

Some Recipes to Play With


Roac Irish Stout

This is one of our older stout recipes. It is an extract recipe with grains. It was appropriately bottled on Saint Patricks Day, 1996. I needn't tell you that it was wonderful!

Ingredients (Five Gallons)
Telford's Amber Malt Extract 6.0 lbs.
Alexander's Light Extract 1.4 lbs.
Roasted Barley 1.0 lbs.
Chocolate Malt 0.25 lbs.
Belgian Special-B Malt 0.25 lbs.
Dark Molasses 3 Table Spoons
Brewtek Irish Stout Yeast 1 Starter
Columbus Hops (12.4%) 1.0 oz. 60 minutes

Dorwinion Orchards Pomegranite Barleywine

The basics of this recipe are standard extract. Add the pomegranite juice during the secondary fermentation. Scottish Ale Yeast can tolerate more alcohol than other yeasts, so there is no need to use a wine or mead yeast for this barleywine.

Ingredients (Five Gallons)
Telford's Extra Pale Malt Extract 12.0 lbs.
Crystal Malt 95-115L 1.0 lbs.
Crystal Malt 40L 1.5 lbs.
Crystal Malt 10L 0.50 lbs.
Fresh Pomegranite Juice One Gallon
Scottish Ale Yeast 1 Starter
Northern Brewer Hops (8.8%) 2.0 oz. 60 minutes

Pelgran's Prestidigitator Dopplebock

This is a very standard extract recipe. Ferment cold and Lager at 42 degrees F for one month. It should have had some dextrin malt for that "chewy" bock texture you get off of Salvator's.

Ingredients (Five Gallons)
German Gold Malt Extract 6.0 lbs.
German Dark Malt Extract 6.0 lbs.
Crystal Malt 80L 1.0 lbs.
German Black Malt 0.25 lbs.
Bavarian Lager Yeast 1 Starter
Styrian Hops (5.2%) 1.0 oz. 60 minutes
Columbus Hops (5.2%) 1.0 oz. 40 minutes
Columbus Hops (5.2%) 0.5 oz. 20 minutes
Columbus Hops (5.2%) 0.5 oz. 6 minutes

Orthanc Old Ale

This is a different version of an British Old Ale, with ingredients from England, Scotland, Canada and the United States. With an OG of 1070, it's on the verge of becoming a barleywine. This also makes a great winter warmer.

You need to do a basic infusion mash for this recipe. Use 4 1/8 gallons strike water at 172F. The mash should settle in at 154F. Mash for one hour, and boil for 90 minutes. We got some chill haze with this one, but that's acceptable for an old ale.

Ingredients (Five Gallons)
English Pale Ale Malt 10.0 lbs.
Canadian Honey Malt 4.0 lbs.
Scottish Crystal Malt 90L 2.0 lbs.
Molasses 0.25 lbs.
Brewtek Classic British Ale Yeast 1 Starter
Columbus Hops (12.4%) 1.0 oz. 60 minutes
Columbus Hops (12.4%) 0.5 oz. 40 minutes
Columbus Hops (12.4%) 0.5 oz. 20 minutes

Old Norbury Barleywine

This barleywine was done in a scottish style. In fact, it's a heavier wee heavy! It has a lot of residual sweetness and heavy maltiness. This is a sipping beer. An award winner.

Ingredients (Five Gallons)
British Amber Malt Extract 12.0 lbs.
Alexander's Light Kicker 1.4 lbs.
Hugh Baird 50-60L Crystal Malt 1.0 lbs.
Brewtek Classic British Ale Yeast 1 Starter
Kent Goldings Hops (4.0%) 1.5 oz. 45 minutes
Kent Goldings Hops (4.0%) 1.0 oz. 20 minutes
Irish Moss 1.0 tsp. 20 minutes

At the start of the boil, put the kicker extract into a seperate pot with one gallon of water and boil separately. At the end of the hour, the separate boil should have thickened considerably to a syrup. Add this back to the main boil. This separate boil causes caramelization, and adds a great complexity to the malt flavor. Scotch ales also have this character, since Scottish brewers started their boil just as soon as the bottom of the kettle was covered with the first runnings.


Oliphaunt Oktoberfest

This is a mash recipe, as you can't get the unique character of Vienna and Munich malts from an extract. It must also be lagered. This is a wonderfully malty brew worthy of beirgartens and hofbraus.

This is mashed with a step infusion. You can also use a single decoction. The first rest is at 122 degrees F for 20 minutes. The second rest is at 147 degrees F for 60 minutes. After the secondary fermentation, which can take quite a while, lager the beer at 40 to 45 degrees for one month.

Ingredients (Five Gallons)
American Two-Row Lager Malt 2.0 lbs.
Vienna Malt 5.0 lbs.
Munich Malt 5.0 lbs.
Crystal Malt, 40L 1.0 lbs.
Bavarian Lager Yeast 1 Starter
Liberty Hops (3.3%) 0.5 oz. 60 minutes
Liberty Hops (3.3%) 0.5 oz. 50 minutes
Liberty Hops (3.3%) 0.5 oz. 40 minutes
Liberty Hops (3.3%) 0.5 oz. 30 minutes
Liberty Hops (3.3%) 0.5 oz. 20 minutes
Liberty Hops (3.3%) 0.5 oz. 10 minutes

Peregrine Porter

Nothing unusual about this recipe. It's a standard extract beer. One could increase the chocolate malt to one pound for a blacker, more roasty beer, or change the black patent to roasted barley for a stout-like porter. I wanted a London type ale yeast, but the homebrew store was out, and the Scottish yeast looked interesting. It gives it a great malty flavor

Ingredients (Five Gallons)
Telford's Extra Light Malt Extract 6.0 lbs.
Alexander's Light Malt Extract 1.4 lbs.
Crystal Malt, 90L 0.75 lbs.
Crystal Malt, 40L 0.25 lbs.
Chocolate Malt 0.75 lbs.
Black Patent Malt 0.25 lbs.
Wyeast's Scottish Ale Yeast 1 Smack Pack
Columbus Hops (12.8%) 1.0 oz. 60 minutes
Columbus Hops (12.8%) 0.5 oz. 20 minutes
Columbus Hops (12.8%) 0.5 oz. 10 minutes

Corsair IPA

An all grain brew. Use an infusion mash with a rest of 155 F, and boil for one half hour before adding your first hops. There is no dry-hopping, but by all means, feel free to do so. IPA's are perfect for hopheads, so go hop away. This IPA has a wonderful orange-gold color. By the way, this was our first mash, done up in May 1996.

Ingredients (Five Gallons)
Hugh-Baird Pale Ale Malt 10.0 lbs.
Hugh-Baird 50-60L Crystal Malt 1.0 lb.
Brewtek American Micro II Yeast Slant 1 starter
Burton Salts 1.0 tsp.
Columbus Hops (12.4%) 1.0 oz. 60 minutes
Centennial Hops (9.9%) 1.0 oz. 30 minutes
Cascade Hops (5.4%) 1.0 oz. 2 minutes

Dale-on-Erebor Steam Beer

Before we made our first real lager, we went half-way with a steam beer. Also known as a California common beer, it is fermented with a lager yeast, but at ale temperatures. Ferment at 55 degrees F, and lager for month at 45 F. This was mashed with a single infusion, as above.

Ingredients (Five Gallons)
Klages Malt 9.5 lbs.
Dextrine Malt 0.5 lbs.
Munich Malt 0.5 lbs.
Crystal Malt, 60L 0.75 lbs.
Brewtek California Gold Yeast 1 Starter
Northern Brewer Hops (9.0%) 1.0 oz. 60 minutes
Northern Brewer Hops (9.0%) 0.5 oz. 45 minutes
Liberty Hops (4.5%) 0.5 oz. 20 minutes
Liberty Hops (4.5%) 0.5 oz. 2 minutes

©David Johnson, Stephen Lowrie, 1997 - 2008
Permission is given to freely copy and redistribute this document.


Previous Contents Next qbrew-0.4.1/docs/handbook.docbook0000644000175100017510000006265711016417220015667 0ustar daviddavid QBrew'> ]> The QBrew Handbook DavidJohnson
david@usermode.org
1999 - 2008 David Johnson Permission to use, copy, modify and distribute this document for any purpose and without fee is hereby granted in perpetuity, provided that the above copyright notice and this paragraph appear in all copies.
Introduction &qbrew; is a homebrewer's recipe calculator. With it a brewer can formulate new recipes and calculate gravity, bitterness, color and other attributes. No payment or registration is required for &qbrew;, but the author would be extremely delighted to receive spontaneous and voluntarily donated bottles filled with homebrew. Photos of such bottles will do in a pinch. If you wish to contribute to the further inebriation of the author, please send mail to david@usermode.org requesting a physical address to send the bottles to. The homepage for QBrew is http://www.usermode.org/code.html Code and cash contributions will, of course, grease the wheels of QBrew development. Using &qbrew;
Recipes
Basic Recipe Details Enter in the basic recipe details in the top section of the program window. There are fields for the title of the recipe, the name of the brewer, recipe style, and batch size. The name of the recipe and the brewer are for identification purposes only. The recipe style will display the appropriate style parameters in the characteristics area. The batch size affects many calculations.
Recipe Ingredients Recipe ingredients are added using the bottom section of the program window. There are tabs for grains and other fermentables, hops, and miscellaneous ingredients. You can edit any field of an ingredient by double clicking within it. To enter a new ingredient, double click in the name field of a blank row. To remove an ingredient, set its name field to a blank. The Notes tab of this section allows you to enter notes on the recipe and batches. These could be specific brewing procedures, brewing and bottling dates, or measured gravity. They are saved along with the recipe, but otherwise do not affect it.
Recipe Characteristics The middle section of the program window details your recipe's characteristics. Information included here are the recommended gravity, bitterness and color for the selected recipe style, as well as the calculated gravity, bitterness, color and alcoholic content for your recipe.
Miscellaneous
Alcohol Percentage Tool You can calculate the actual alcohol percentage in a batch by using the Alcohol Percentage tool under the Tools menu. Enter in your measured original gravity and final gravity. The percentage of alchohol by volume and by weight will be calculated.
Hydrometer Correction Tool You can access the Hydrometer Correction tool under the Tools menu. This tool is useful for converting your actual hydrometer readings to the correct value. Enter in the temperature at which you took the sample, the temperature at which the hydrometer is calibrated (typically 60° or 68° Fahrenheit), and the actual hydrometer reading. The corrected reading will be calculated.
QBrew Data The data that &qbrew; uses is in the file qbrewdata, typically located under the install directory in Windows, as part of the application bundle under Macintosh OSX, and under /usr/local/share/qbrew under traditional Unix. If there exists a file in the user's home directory with the name of .qbrewdata, then this file will be used instead. The Database Editor, under the Tools menu, may be used to edit the users's .qbrewdata file. The operation of the editor is similar to editing ingredients in a recipe. In addition there is a Style page for editing recipe styles.
US/Metric Conversions US units of measurement are used by default in &qbrew;. This can be changed in the configuration dialog. Both US and Metric measurements are available. Please note that converting from one system to another and back may result in round-off errors during the conversion.
Exporting and Importing Data QBrew will import BeerXML recipes, and export native recipes to HTML, PDF, BeerXML or plain text format. BeerXML is a new format designed for sharing recipes and other brewing data. QBrew supports version 1.0. Importing or exporting a recipe will result in some loss of data. This is because brewing programs all have different underlying assumptions and models for the data. Because of this, QBrew recipes should be stored in the native QBrew format, and only exported for sharing or posting recipes.
&qbrew; Reference
Configuration The Configuration dialog is used configuring &qbrew;. There are four buttons on the bottom of the dialog. Pressing the OK button will apply all settings and close the dialog. Pressing the Reset button will reset the dialog to default values. Pressing the Apply button will apply the settings, but not close the dialog. The Cancel button will close the dialog without applying the settings. Configuration changes are automatically saved when the OK button is pressed.
General Configuration The "Look and feel" control allows you to change the look and feel (theme) of &qbrew;. "Show splash screen" lets you choose to display or not display the program startup splash screen. The "Recent files" control selects the number of files displayed in the "Open Recent" menu. The "Enable autosave" controls set whether automatic saving of the current recipe is done, and how often. The "Enable autobackup" control sets whether recipes are automatically backed up. The "Load last file" control determines whether the previously used recipe is automatically loaded upon program startup.
Recipe Configuration The "Recipe Defaults" section sets some default recipe values. These settings only affect new recipes or ingredients. They include "Batch size", "Recipe style", and "Hop type".
Calculation Configuration Use the "Measurement units" control to set the measurement units you will be using (US or Metric). Use the "Tinseth" and "Morey" controls to alter the bitterness and color calculations. "Efficiency" sets the mash efficiency for calculations.
Designer's Notes &qbrew; aims to be a simple streamlined homebrewing recipe calculator. As such, it doesn't include features that other software designed for professional brewing might include. This is not a flaw on the part of &qbrew;, but a conscious choice to moderate the scope of the software. Emails and forum posts praising the &qbrew; interface tell me that his was a good decision.
Grain Calculations The basic gravity calculation is the sum of all grains' extract times quantity, divided by the batch size. Mashed grains will have their extract modified by the mash efficiency. Steeped grains will use a lesser efficiency, typically half of their extract. The color calculated is in SRM, not in HCU (homebrew color units). HCU is the sum of all grains' color times quantity, divided by the batch size. This is only accurate for very low color values. The default conversion to SRM uses the formula discovered by Ray Daniels, which is SRM=(MCUx0.2)+8.4 (for values of HCU over 8.0). An alternate color calculation discovered by Dan Morey is SRM=1.4922x[(MCU)^0.6859] (for values of SRM less than 50). To change to the Morey method, open up the Configure... dialog and check the Use Morey color calculation box. More information on color calculations can be found at "Approximating SRM Beer Color" <http://hbd.org/babble/Color.htm>, and "Beer's Law" <http://www.brewingtechniques.com/brewingtechniques/beerslaw/morey.html>. Final gravity is assumed to be 25% of the OG. Looking through the AHB style guide, this is the same assumption they made. The alcoholic content calculations were derived from Noonan's New Brewing Lager Beer, and based on this FG assumption.
Hop Calculations There are two different methods used to calculate hop bitterness. The default IBU calculation uses the Rager method, from Norm Pyle's "Hops FAQ" <http://realbeer.com/hops/FAQ.html>. The other method is the Tinseth method, from Glen Tinseth's "Hop Page" <http://realbeer.com/hops/>. To change to the Tinseth method, open up the Configure... dialog and check the Use Tinseth bitterness calculation box. The utilization table for the Rager method can be edited by changing the appropriate area of the qbrewdata file. Please see the above links for details on these formulae, as they can be a bit complex.
Questions and Answers Why don't the results on the Alcohol tool match the Characteristics section of the main window? It's the curse of rounding errors! When you use the Alcohol tool you are entering in gravity values with a precision of two decimal places. But your recipe in QBrew is stored with a much higher precision than this, even though it is displaying characteristics with a lower precision. I get hundreds of errors when I try to compile &qbrew; under Unix. What am I doing wrong? &qbrew; has been successfully compiled and run on a variety of Unix platforms, so the odds are low that something's wrong with configure (although it does happen). More likely, you don't have the necessary libraries or headers installed. Make sure that you have the X development libraries installed, as well as the Qt &qt.version; or greater development libraries. &qbrew; will not compile and link with the older Qt 3.x libraries. There are many useful options in the configure script. These options can be displayed by running ./configure --help. Also please read the INSTALL file for several useful tips. If you are still having problems after this, then write me at qbrew@usermode.org. Please include the full text of the error message, along with the config.log in the build directory. Why don't you distribute &qbrew; as a DEB or RPM file? I do make packages available for Windows and Mac OSX. I am doing this because users of those platforms are unaccustomed to building software. It is more difficult to do this under Linux and Unix because there are so too many different varieties available. I cannot possibly distribute binary packages for each system and distribution while trying to keep up with changing versions. At this time, &qbrew; is already available prepackaged for Debian GNU/Linux, SuSE Linux, Linspire, NetBSD and FreeBSD. If you distribution does not have a &qbrew; package, you may wish to consider maintaining one for them. Please note that the available binary packages are not always the most recent &qbrew; version. Credits
Helpful Folk I would like the thank the following people for their outstanding help with the creation of &qbrew;... ...Abe Kabakoff for contributing several formulae and the Correction tool. ...Michal Palczewski for contributing bug fixes and the Alcohol tool. ...Lee Henderson, Rob Hudson, and Kevin Pullin for contributing bug fixes. ...Tobias Toedter for German translation work. ...Stephen Lowrie for a lot of rote work with the qbrewdata file, as well as major portions of the brewing primer. ...the various maintainers for &qbrew; packages. ...The numerous users who have written me and kept my spirits up.
qbrew-0.4.1/docs/sqa.html0000644000175100017510000011476111016417220014204 0ustar daviddavid QBrew Test Procedure

QBrew Test Procedure

Setup

This procedure will make use of the the included files paleale.qbrew and stout.qbrew. In addition, the Configure dialog should be set with default values using the Defaults buttons.

User Interface

Command Line

  • [___] Verify that the following command line options result in the indicated behavior under Unix:
    file Open the specified file in QBrew
    -help Print the command line options
    -version Print the version number of QBrew
  • [___] Verify that all other command line options displayed by the -help option are currently used by Qt and behave as described.

Menubar

  • [___] Verify that all menu accelerators work and are mapped to the appropriate keystroke
  • [___] Verify that an appropriate help message is displayed in the statusbar when each menu item is highlighted

File Menu

  • [___] Verify that the File->New menu item creates a new, blank recipe
  • [___] Verify the File->Open... menu item opens an existing recipe file
  • [___] Verify the File->Open Recent item displays a submenu of recently opened files
  • [___] Verify the File->Save menu item save the current recipe to a recipe file under the same file name. If this is a new recipe, verify this behaves identically to the File->Save as... menu item
  • [___] Verify the File->Save as... menu item saves the current recipe to a recipe file, prompting for a new file name, and prompting to overwrite existing files if applicable
  • [___] Verify the File->Export... menu item exports the current recipe to a different format, prompting for a filename, and prompting to overwrite existing files if applicable
  • [___] Verify the File->Print Preview... menu item brings up a print preview dialog
  • [___] Verify the File->Print... menu item brings up a print dialog that prints the recipe
  • [___] Verify the File->Quit menu item quits the application, after prompting to save any unsaved recipes

Tools Menu

  • [___] Verify the Tools->Alcohol Percentage menu item brings up the Alcohol Tool window
  • [___] Verify the Tools->Hydrometer Correction menu item brings up the Hydrometer Tool window
  • [___] Verify the Tools->Database Editor menu item brings up the database editor

Options Menu

  • [___] Verify the Options->Main Toolbar menu item toggles the toolbar between enabled and disabled states
  • [___] Verify the Options->Statusbar menu item toggles the statusbar between enabled and disabled states
  • [___] Verify the Options->Configure... menu item brings up the configure dialog

Help Menu

  • [___] Verify the Help->Contents... menu item brings up a browser window displaying the application's user manual
  • [___] Verify the Help->Primer... menu item brings up a browser window displaying a brewing primer
  • [___] Verify the What's This? menu item brings up cursor used to select help on specific controls
  • [___] Verify the Help->About... menu item brings up a message box displaying the application's copyright information

Toolbar

  • [___] Verify that the following buttons are on the toolbar by default: New, Open, Save, Print, and Context
  • [___] Verify that all buttons in the toolbar correspond to a menu item in the menubar
  • [___] Verify that each button in the toolbar has an appropriate tooltip and an appropriate Context help entry
  • [___] Verify that the toolbar can be docked to each side of the application

Statusbar

  • [___] Verify that the statusbar has only a message area
  • [___] Verify that the statusbar displays no message longer than two seconds

Configure Dialog

  • [___] Verify that all controls are visible in the dialog box
  • [___] Verify that pressing OK or Cancel removes the dialog window
  • [___] Verify that pressing Apply of OK makes all changes take effect immediately (as appropriate)
  • [___] Verify that pressing the Reset button resets all dialog controls to default values
  • [___] Verify that all options in a dialog page are related to the title of the page
  • [___] Verify that the Look and feel combobox is used to select a Qt widget style
  • [___] Verify that the Show splash screen checkbox is used to select whether the splash screen is shown at program start
  • [___] Verify that the Number of recent files spinbox selects the number of files displayed in the Open Recent submenu
  • [___] Verify that the Enable autosave checkbox and spinbox set autosave functionality, where the currently edited file will be automatically saved according to the selected interval
  • [___] Verify that the Enable autobackup checkbox sets autobackup functionality, where a backup will be created upon saving changes to a recipe
  • [___] Verify that the Load last file checkbox will cause the application to automatically load the last recipe used
  • [___] Verify the Measurement units combo box contains entries for Metric and US
  • [___] Verify that while the Measurement units option is set to Metric, all subsequent units will be in Metric (grams, kilograms, liters)
  • [___] Verify that while the Measurement units option is set to US, all subsequent units will be in US measurements (ounces, pounds, gallons)
  • [___] Verify that the Mash efficiency spinbox sets the recipe mash efficiency, in the range of 0.00 to 1.00 in increments of 0.01
  • [___] Verify that the Steep yield spinbox sets the yield for steeped grains, in the range of 0.00 to 1.00 in increments of 0.01
  • [___] Verify that the Use Tinseth checkbox toggles the application calculation routines to use the Tinseth bitterness formula instead of the default (Rager) bitterness formula
  • [___] Verify that the Use Morey checkbox toggles the application calculation routines to use the Morey color formula instead of the default (Daniels) color formula
  • [___] Verify that the Batch size spinbox selects the default back size for new recipes, in the range of 0.00 to 100.00 in increments of 0.25
  • [___] Verify that the Recipe style combobox selects the default recipe style for new recipes
  • [___] Verify that the Hop Type combobox selects the default hop type for new hop ingredients

Alcohol Tool

  • [___] Verify that the Original gravity spinbox ranges from 0.900 to 1.400 in increments of 0.001
  • [___] Verify that the Final gravity spinbox ranges from 0.900 to 1.400 in increments of 0.001
  • [___] Verify that the Alcohol by Volume label displays the calculated ABV
  • [___] Verify that the Alcohol by Weight label displays the calculated ABW

Hydrometer Tool

  • [___] Verify that the Sample temperature spinbox has the suffix of "°F", and ranges from 32.0 to 212.0 in increments of 0.2. If Metric measurements are used, verify that the suffix is "°C", and ranges from 0.0 to 100.0 degrees.
  • [___] Verify that the Calibrated temperature spinbox has the suffix of "°F", and ranges from 32.0 to 212.0 in increments of 0.2. If Metric measurements are used, verify that the suffix is "°C", and ranges from 0.0 to 100.0 degrees.
  • [___] Verify that the Hydrometer reading spinbox has no suffix and ranges from 0.850 to 1.200 in increments of 0.001
  • [___] Verify that the Corrected reading label has no suffix, and changes as the values in the spinboxes change

Database Editor

Grains

  • [___] Verify that selecting the Grains tab changes the list section to controls for grain ingredients
  • [___] Verify that the list of ingredients in the displayed in the list section matches the selection available in the database file
  • [___] Verify that the Grain field can be edited by the user
  • [___] Verify that the Extract field can be edited and its spinbox ranges from 1.000 to 1.100 in increments of 0.001
  • [___] Verify that the Color field can be edited and its spinbox ranges from 0.0 to 500.0 in increments of 1.0
  • [___] Verify that the Type field combobox has the entries of Grain, Extract, Adjunct, Sugar and Other, and that the combobox is not user editable.
  • [___] Verify that the Use field combobox has the entries of Extract, Mashed, Steeped, and Other, and that the combobox is not user editable
  • [___] Verify that setting the Grain field of an entry to blank removes the grain from the list
  • [___] Verify that attempting to add a grain that already exists in the database will prompt a confirmation dialog

Hops

  • [___] Verify that selecting the Hops tab changes the list section to controls for hop ingredients
  • [___] Verify that the list of ingredients in the displayed in the list section matches the selection available in the database file
  • [___] Verify that the Hop field combobox can be edited by the use
  • [___] Verify that the Alpha field has a spinbox with a suffix of "%", and ranges from 0.0 to 50.0 in increments of 0.1
  • [___] Verify that setting the Hop field of an entry to blank removes the hop from the list
  • [___] Verify that attempting to add a hop that already exists in the database will prompt a confirmation dialog

Miscellaneous

  • [___] Verify that selecting the Miscellaneous tab changes the ingredients section to controls for miscellaneous ingredients
  • [___] Verify that the list of ingredients in the displayed in the list section matches the selection available in the database file
  • [___] Verify that the Misc field has a combobox that can be edited by the user
  • [___] Verify that the Type field has a combobox with the entries Yeast, Fining, Herb, Spice, Flavor, Additive and Other.
  • [___] Verify that the Notes field has an editbox which is editable by the user
  • [___] Verify that setting the Misc field of an entry to blank removes the ingredient from the list
  • [___] Verify that attempting to add an ingredient that already exists in the database will prompt a confirmation dialog

Styles

  • [___] Verify that selecting the Styles tab changes the list section to controls for styles
  • [___] Verify that the list of styles in the displayed in the list section matches the selection available in the database file
  • [___] Verify that the Style field has a combobox that can be edited by the user
  • [___] Verify that the Min. OG and Max. OG fields have spinboxes that range from 1.000 to 1.150 in increments of 0.001
  • [___] Verify that the Min. OG spinbox has a maximum value equal to the minimum value of the Max. OG spinboxes, and vice versa
  • [___] Verify that the Min. FG and Max. FG fields have spinboxes that range from 1.000 to 1.150 in increments of 0.001
  • [___] Verify that the Min. FG spinbox has a maximum value equal to the minimum value of the Max. FG spinboxes, and vice versa
  • [___] Verify that the Min. IBU and Max. IBU fields have spinboxes that range from 0 to 120 in increments of 1
  • [___] Verify that the Min. IBU spinbox has a maximum value equal to the minimum value of the Max. IBU spinboxes, and vice versa
  • [___] Verify that the Min. SRM and Max. SRM fields have spinboxes that range from 0 to 50 in increments of 1
  • [___] Verify that the Min. SRM spinbox has a maximum value equal to the minimum value of the Max. SRM spinboxes, and vice versa
  • [___] Verify that setting the Style field of an entry to blank removes the style from the list
  • [___] Verify that attempting to add an style that already exists in the database will prompt a confirmation dialog

Help Browser

  • [___] Verify that the Contents and Primer help browser contains File->Print..., File->Quit, Navigate->Backward, Navigate->Forward, and Navigate->Home menu items.
  • [___] Verify that the Contents and Primer help browser contains Back, Forward, Home, Print and Quit toolbar buttons
  • [___] Verify that local links embedded in the Contents and Primer help browser content are active and usable

Recipe and Characteristics

  • [___] Verify that the Title and Brewer editboxes have no effect on any recipe calculations or other displayed information
  • [___] Verify that the Style combobox displays a list of beer styles corresponding to all style entries in the qbrewdata file
  • [___] Verify that changing the Style combobox entry immediately changes the Minimum Gravity, Maximum Gravity, Minimum Bitterness, Maximum Bitterness, Minimum Color and Maximum Color labels in the Characteristics frame, as well as the Characteristics frame's title
  • [___] Verify that the Size spinbox displays the application batch size (set in the Configure dialog) by default, with a range from 0.00 to 5000.00 in increments of 0.25
  • [___] Verify that any changes to the Size spinbox results in an immediate change to the Recipe Gravity, Recipe Bitterness, Recipe Color, Estimated FG, Alcohol by Volume and Alcohol by Weight labels in the Characteristics frame
  • [___] Verify that the Characteristics frame displays style information in normal text, and recipe information in bold text

Grains

  • [___] Verify that selecting the Grains tab changes the ingredients section to controls for grain ingredients
  • [___] Verify that the combobox for the Grain field contains a list of grains corresponding to those in the qbrewdata database
  • [___] Verify that the edit field of the Grain combobox can be edited by the user
  • [___] Verify that the Quantity spinbox has the suffix of "lb" by default, and ranges from 0.00 to 1000.00 in increments of 0.25
  • [___] Verify that the Extract spinbox ranges from 1.000 to 1.100 in increments of 0.001
  • [___] Verify that the Color spinbox ranges from 0.0 to 500.0 in increments of 1.0, and has a suffix of a degree sign.
  • [___] Verify that the Type field combobox has the entries of Grain, Extract, Adjunct, Sugar and Other, and that the combobox is not user editable.
  • [___] Verify that the Use combobox has the entries of "extract", "mashed", "steeped" and "other", and that the combobox edit field is not user editable
  • [___] Verify that setting the Grain field of an entry to blank removes the grain from the list
  • [___] Verify that using the [+] and [-] buttons will add or remove a grain from the list

Hops

  • [___] Verify that selecting the Hops tab changes the ingredients section to controls for hop ingredients
  • [___] Verify that the combobox for the Hop field contains a list of hops corresponding to those in the qbrewdata database
  • [___] Verify that the edit field of the Hop combobox can be edited by the user
  • [___] Verify that the Quantity spinbox has the suffix of "oz" by default, and ranges from 0.00 to 1000.00 in increments of 0.25
  • [___] Verify that the Type combobox includes the entries of "Pellet", "Plug" and "Whole" by default, and that the edit field of the combox is user editable
  • [___] Verify that the Alpha spinbox has a suffix of "%", and ranges from 0.0 to 50.0 in increments of 0.1
  • [___] Verify that the Time spinbox has a suffix of "min", and ranges from 0 to 120 in increments of 5
  • [___] Verify that setting the Hop field of an entry to blank removes the hop from the list
  • [___] Verify that using the [+] and [-] buttons will add or remove a hop from the list

Miscellaneous

  • [___] Verify that selecting the Miscellaneous tab changes the ingredients section to controls for miscellaneous ingredients
  • [___] Verify that the combobox for the Misc field contains a list of ingredients corresponding to those in the qbrewdata database
  • [___] Verify that the edit field of the Misc combobox can be edited by the user
  • [___] Verify that the Quantity spinbox has the suffix of "unit" by default, and ranges from 0.00 to 1000.00 in increments of 0.25
  • [___] Verify that the Notes editbox is editable by the user
  • [___] Verify that setting the Misc field of an entry to blank removes the ingredient from the list
  • [___] Verify that using the [+] and [-] buttons will add or remove an ingredient from the list

Notes

  • [___] Verify that the Recipe Notes edit box can be edited with user text
  • [___] Verify that the Recipe Notes are initially empty on a new file, upon started the application and upon selecting a new recipe after the notes have been modified
  • [___] Verify that the Recipe Notes can be saved with a recipe, and restored upon opening a recipe
  • [___] Verify that the Batch Notes edit box can be edited with user text
  • [___] Verify that the Batch Notes are initially empty on a new file, upon started the application and upon selecting a new recipe after the notes have been modified
  • [___] Verify that the Batch Notes can be saved with a recipe, and restored upon opening a recipe

Calculations

Using the indicated files, verify the following values in the Characteristics frame:

  • [___] Verify with a qbrewdata file from version 0.3.0, that the DATA_PREVIOUS in resource.h value is still valid.
  • [___] Verify with the paleale.qbrew recipe, that changing the recipe Size to 10.0 results in a Gravity of 1.029, Bitterness of 26, Color of 8 without the Use Morey checked (6 with), Estimated FG of 1.007, Alcohol by Volume of 2.8 and Alcohol by Weight of 2.2
  • [___] Verify with the stout.qbrew recipe, that changing the Mash efficiency to 83 results in a Gravity of 1.078, Bitterness of 80, Estimated FG of 1.019, Alcohol by Volume of 7.5, and Alcohol by Weight of 5.9, and that Color has not changed
  • [___] Verify with the paleale.qbrew recipe, that changing the Extract of the Light malt extract to 1.044 results in a Gravity of 1.09, Bitterness of 47, and that Color has not changed
  • [___] Verify with the stout.qbrew recipe, that changing the Use of the British chocolate malt to "steeped" results in a Gravity of 1.067, Bitterness of 84, and that Color has not changed
  • [___] Verify with the paleale.qbrew recipe, that adding 5 minutes to each hop Time results in a Bitterness of 59 without the Use Tinseth checked, 56 with the Use Tinseth checked, and that Gravity and Color have not changed
  • [___] Verify with the stout.qbrew recipe, that changing the Alpha for all hops to 9.6 results in a Bitterness of 86 without the Use Tinseth checked, 72 with the Use Tinseth checked, and that Gravity and Color have not changed
  • [___] Verify with either recipe, that any changes to Miscellaneous ingredients do not change any recipe characteristics

Without changing any default values, create a new recipe with the 7.0 pounds of mashed British two-row (1.038 extract, 2.5 color), 1 pound of steeped CaraMunich (1.033 extract, 75.0 color), 1 ounce of pellet Tettnanger (3.7 alpha) at 60 minutes, 0.5 ounces of whole Saaz (3.5 alpha) at 10 minutes, and 1 unit of Lager yeast

  • [___] Verify that this recipe results in a Gravity of 1.043, Bitterness of 17 without Use Tinseth checked (16 with), Color of 12 without Use Morey checked (11 with), Estimated FG of 1.011, Alcohol by Volume of 4.2 and Alcohol by Weight of 3.3

Using the Hydrometer Tool, verify the following:

  • [___] Verify that entering a Sample temperature of 212.0, a Calibrated temperature of 32.0, and a Hydrometer reading of 1.100 results in a Corrected reading of 1.148
  • [___] Verify that entering a Sample temperature of 32.0, a Calibrated temperature of 212.0, and a Hydrometer reading of 1.000 results in a Corrected reading of 0.96
  • [___] Verify that entering a Sample temperature of 80.0, a Calibrated temperature of 60.0, and a Hydrometer reading of 1.065 results in a Corrected reading of 1.067
  • [___] Verify that entering a Sample temperature of 85.2, a Calibrated temperature of 68.0, and a Hydrometer reading of 1.077 results in a Corrected reading of 1.080

Set the configuration to Metric units, batch size of 15 liters, and enter the following ingredients into a new recipe: mashed, American two-row 4.0kg 1.037 extract 1.8 color, Columbus hops 10g pellet 15.4 alpha 60 time.

  • [___] Verify that the Recipe Gravity is 1.062, the Recipe Bitterness is 27, the Recipe Color is 4, the ABV is 6.0 and the ABW is 4.7
  • [___] Verify that entering into the Alcohol tool an Original gravity of 1.058 and a Final gravity of 1.012 results in an ABV of 5.9% and ABW of 4.7%
  • [___] Verify that entering into the Alcohol tool an Original gravity of 1.070 and a Final gravity of 1.018 results in an ABV of 6.7% and ABW of 5.3%

Documentation

  • [___] Verify that the user manual available under the Help->Contents... contains correct version and copyright dates.
  • [___] Verify that window titles for all online documentation are formatted correctly. Errors can arise form newlines and nbsp tags.
  • [___] Verify that The Menus sections of the user manual contain all available menu commands, with no unavailable commands
  • [___] Verify that the Using QBrew sections of the user manual are an accurate portrayal of the QBrew application
  • [___] Verify that the primer available under the Help->Primer... contains correct copyright dates.
  • [___] Verify that the information presented in the primer is not dependant upon the use of the QBrew application
  • [___] Verify that the ChangeLog and README files have the correct version and copyright dates
  • [___] Verify that the html documentation is viewable in a variety of browsers. Using the Konqueror validation tool, or similar, validate the html.
  • [___] Verify that miscellaneous documentation contained in the win and mac directories are up to date and accurate.

Miscellaneous

  • [___] Verify that the text file created with the File->Export... command using the text format, is well formatted within the limitations of plain text
  • [___] Verify that the HTML file created with the File->Export... command using the HTML format, is well formatted. Use tidy for verification
  • [___] Verify that all controls (other than labels) have What's This help available
  • [___] Verify by using ldd or a similar command or method, that the requirements for QBrew are limited to POSIX, X11 and Qt (and the requirements for those libraries)
  • [___] Verify that the default file extension for recipe files is "qbrew"
  • [___] Verify that the default application configuration file on Unix is ~/.config/usermode/qbrew.conf, and that the system registry or preferences facility is used under Windows or Macintosh.
  • [___] Verify that if a file by the name of .qbrewdata is present in the user's home directory, that the data in this file will be used instead of the defaults
  • [___] Verify that the following files are installed by default to /usr/local/share/qbrew: qbrewdata, splash.png, and any available translations
  • [___] Verify that all files under /usr/local/share/doc/qbrew with the prefix of handbook- form a complete user manual navigable with "Prev" and "Next" links
  • [___] Verify the existance of LICENSE,and README, files under /usr/local/share/doc/qbrew
  • [___] Verify that using the instructions given in the INSTALL file, that QBrew can be built and executed on a variety of Unix and unix-like operating system
  • [___] Verify that the ui files in the source directory have Qt versions of 4.0.
  • [___] Verify that the correct list of contributors is contained in the About dialog and the documentation
qbrew-0.4.1/docs/builddocs0000755000175100017510000000217311016417220014421 0ustar daviddavid#! /bin/sh STYLESHEET=handbook.xsl HANDBOOK=handbook.docbook # check for xsltproc xsltfound=`which xsltproc` if [ -z "$xsltfound" ] ; then echo "Error: xsltproc not found!" echo "Please make sure xsltproc is installed and in your path" exit fi # check for sed sedfound=`which sed` if [ -z "$sedfound" ] ; then echo "Error: sed not found!" echo "Please make sure sed is installed and in your path" exit fi # check for tidy tidyfound=`which tidy` if [ -z "$tidyfound" ] ; then echo "Warning: tidy not found!" fi # create output directory rm -rf book mkdir book cd book # process xml if [ -e ../$HANDBOOK ] ; then echo "Processing $HANDBOOK" xsltproc ../$STYLESHEET ../$HANDBOOK if [ -n "$tidyfound" ] ; then tidy -indent -quiet -modify *.html fi # add prefixes to filenames (would be nice if stylesheet could do this) htmllist=`ls *.html` for htmlfile in $htmllist ; do filelist=`ls *.html` for temp in $filelist ; do sed -i .bak -e s/"$htmlfile"/handbook-$htmlfile/g $temp done mv ${htmlfile} handbook-${htmlfile} done rm -f *.bak else echo "WARNING: $HANDBOOK not found" fi cd .. qbrew-0.4.1/docs/handbook.xsl0000644000175100017510000000117511016417220015041 0ustar daviddavid 1 0 1 qbrew-0.4.1/docs/book/0000755000175100017510000000000011014465725013465 5ustar daviddavidqbrew-0.4.1/docs/book/handbook-configuration.html0000644000175100017510000001432011014465332020777 0ustar daviddavid Configuration

Configuration

The Configuration dialog is used configuring QBrew. There are four buttons on the bottom of the dialog. Pressing the OK button will apply all settings and close the dialog. Pressing the Reset button will reset the dialog to default values. Pressing the Apply button will apply the settings, but not close the dialog. The Cancel button will close the dialog without applying the settings.

Configuration changes are automatically saved when the OK button is pressed.

General Configuration

The "Look and feel" control allows you to change the look and feel (theme) of QBrew. "Show splash screen" lets you choose to display or not display the program startup splash screen.

The "Recent files" control selects the number of files displayed in the "Open Recent" menu. The "Enable autosave" controls set whether automatic saving of the current recipe is done, and how often. The "Enable autobackup" control sets whether recipes are automatically backed up. The "Load last file" control determines whether the previously used recipe is automatically loaded upon program startup.

Recipe Configuration

The "Recipe Defaults" section sets some default recipe values. These settings only affect new recipes or ingredients. They include "Batch size", "Recipe style", and "Hop type".

Calculation Configuration

Use the "Measurement units" control to set the measurement units you will be using (US or Metric). Use the "Tinseth" and "Morey" controls to alter the bitterness and color calculations. "Efficiency" sets the mash efficiency for calculations.

qbrew-0.4.1/docs/book/handbook-faq.html0000644000175100017510000002067511014464616016715 0ustar daviddavid Chapter 4. Questions and Answers

Chapter 4. Questions and Answers

Q: Why don't the results on the Alcohol tool match the Characteristics section of the main window?
Q: I get hundreds of errors when I try to compile QBrew under Unix. What am I doing wrong?
Q: Why don't you distribute QBrew as a DEB or RPM file?
Q:

Why don't the results on the Alcohol tool match the Characteristics section of the main window?

A:

It's the curse of rounding errors! When you use the Alcohol tool you are entering in gravity values with a precision of two decimal places. But your recipe in QBrew is stored with a much higher precision than this, even though it is displaying characteristics with a lower precision.

Q:

I get hundreds of errors when I try to compile QBrew under Unix. What am I doing wrong?

A:

QBrew has been successfully compiled and run on a variety of Unix platforms, so the odds are low that something's wrong with configure (although it does happen). More likely, you don't have the necessary libraries or headers installed. Make sure that you have the X development libraries installed, as well as the Qt 4.3 or greater or greater development libraries. QBrew will not compile and link with the older Qt 3.x libraries.

There are many useful options in the configure script. These options can be displayed by running ./configure --help. Also please read the INSTALL file for several useful tips.

If you are still having problems after this, then write me at . Please include the full text of the error message, along with the config.log in the build directory.

Q:

Why don't you distribute QBrew as a DEB or RPM file?

A:

I do make packages available for Windows and Mac OSX. I am doing this because users of those platforms are unaccustomed to building software. It is more difficult to do this under Linux and Unix because there are so too many different varieties available. I cannot possibly distribute binary packages for each system and distribution while trying to keep up with changing versions.

At this time, QBrew is already available prepackaged for Debian GNU/Linux, SuSE Linux, Linspire, NetBSD and FreeBSD. If you distribution does not have a QBrew package, you may wish to consider maintaining one for them. Please note that the available binary packages are not always the most recent QBrew version.

qbrew-0.4.1/docs/book/handbook-hop.html0000644000175100017510000000676111014464603016730 0ustar daviddavid Hop Calculations

Hop Calculations

There are two different methods used to calculate hop bitterness. The default IBU calculation uses the Rager method, from Norm Pyle's "Hops FAQ" <http://realbeer.com/hops/FAQ.html>. The other method is the Tinseth method, from Glen Tinseth's "Hop Page" <http://realbeer.com/hops/>. To change to the Tinseth method, open up the Configure... dialog and check the Use Tinseth bitterness calculation box. The utilization table for the Rager method can be edited by changing the appropriate area of the qbrewdata file.

Please see the above links for details on these formulae, as they can be a bit complex.

qbrew-0.4.1/docs/book/handbook-index.html0000644000175100017510000002161611014464575017255 0ustar daviddavid The QBrew Handbook qbrew-0.4.1/docs/book/handbook-intro.html0000644000175100017510000000703611014464566017301 0ustar daviddavid Introduction

Introduction

QBrew is a homebrewer's recipe calculator. With it a brewer can formulate new recipes and calculate gravity, bitterness, color and other attributes.

No payment or registration is required for QBrew, but the author would be extremely delighted to receive spontaneous and voluntarily donated bottles filled with homebrew. Photos of such bottles will do in a pinch. If you wish to contribute to the further inebriation of the author, please send mail to requesting a physical address to send the bottles to.

The homepage for QBrew is http://www.usermode.org/code.html

Code and cash contributions will, of course, grease the wheels of QBrew development.

qbrew-0.4.1/docs/book/handbook-notes.html0000644000175100017510000001334111014464545017267 0ustar daviddavid Chapter 3. Designer's Notes

Chapter 3. Designer's Notes

QBrew aims to be a simple streamlined homebrewing recipe calculator. As such, it doesn't include features that other software designed for professional brewing might include. This is not a flaw on the part of QBrew, but a conscious choice to moderate the scope of the software. Emails and forum posts praising the QBrew interface tell me that his was a good decision.

Grain Calculations

The basic gravity calculation is the sum of all grains' extract times quantity, divided by the batch size. Mashed grains will have their extract modified by the mash efficiency. Steeped grains will use a lesser efficiency, typically half of their extract.

The color calculated is in SRM, not in HCU (homebrew color units). HCU is the sum of all grains' color times quantity, divided by the batch size. This is only accurate for very low color values. The default conversion to SRM uses the formula discovered by Ray Daniels, which is SRM=(MCUx0.2)+8.4 (for values of HCU over 8.0). An alternate color calculation discovered by Dan Morey is SRM=1.4922x[(MCU)^0.6859] (for values of SRM less than 50). To change to the Morey method, open up the Configure... dialog and check the Use Morey color calculation box. More information on color calculations can be found at "Approximating SRM Beer Color" <http://hbd.org/babble/Color.htm>, and "Beer's Law" <http://www.brewingtechniques.com/brewingtechniques/beerslaw/morey.html>.

Final gravity is assumed to be 25% of the OG. Looking through the AHB style guide, this is the same assumption they made. The alcoholic content calculations were derived from Noonan's New Brewing Lager Beer, and based on this FG assumption.

qbrew-0.4.1/docs/book/handbook-using.html0000644000175100017510000001662511014464513017267 0ustar daviddavid Chapter 1. Using QBrew

Chapter 1. Using QBrew

Recipes

Basic Recipe Details

Enter in the basic recipe details in the top section of the program window. There are fields for the title of the recipe, the name of the brewer, recipe style, and batch size.

The name of the recipe and the brewer are for identification purposes only. The recipe style will display the appropriate style parameters in the characteristics area. The batch size affects many calculations.

Recipe Ingredients

Recipe ingredients are added using the bottom section of the program window. There are tabs for grains and other fermentables, hops, and miscellaneous ingredients.

You can edit any field of an ingredient by double clicking within it. To enter a new ingredient, double click in the name field of a blank row. To remove an ingredient, set its name field to a blank.

The Notes tab of this section allows you to enter notes on the recipe and batches. These could be specific brewing procedures, brewing and bottling dates, or measured gravity. They are saved along with the recipe, but otherwise do not affect it.

Recipe Characteristics

The middle section of the program window details your recipe's characteristics. Information included here are the recommended gravity, bitterness and color for the selected recipe style, as well as the calculated gravity, bitterness, color and alcoholic content for your recipe.

qbrew-0.4.1/docs/book/handbook-misc.html0000644000175100017510000001607611014465176017103 0ustar daviddavid Miscellaneous

Miscellaneous

Alcohol Percentage Tool

You can calculate the actual alcohol percentage in a batch by using the Alcohol Percentage tool under the Tools menu. Enter in your measured original gravity and final gravity. The percentage of alchohol by volume and by weight will be calculated.

Hydrometer Correction Tool

You can access the Hydrometer Correction tool under the Tools menu. This tool is useful for converting your actual hydrometer readings to the correct value. Enter in the temperature at which you took the sample, the temperature at which the hydrometer is calibrated (typically 60° or 68° Fahrenheit), and the actual hydrometer reading. The corrected reading will be calculated.

QBrew Data

The data that QBrew uses is in the file qbrewdata, typically located under the install directory in Windows, as part of the application bundle under Macintosh OSX, and under /usr/local/share/qbrew under traditional Unix. If there exists a file in the user's home directory with the name of .qbrewdata, then this file will be used instead.

The Database Editor, under the Tools menu, may be used to edit the users's .qbrewdata file. The operation of the editor is similar to editing ingredients in a recipe. In addition there is a Style page for editing recipe styles.

US/Metric Conversions

US units of measurement are used by default in QBrew. This can be changed in the configuration dialog. Both US and Metric measurements are available.

Please note that converting from one system to another and back may result in round-off errors during the conversion.

Exporting and Importing Data

QBrew will import BeerXML recipes, and export native recipes to HTML, PDF, BeerXML or plain text format. BeerXML is a new format designed for sharing recipes and other brewing data. QBrew supports version 1.0.

Importing or exporting a recipe will result in some loss of data. This is because brewing programs all have different underlying assumptions and models for the data. Because of this, QBrew recipes should be stored in the native QBrew format, and only exported for sharing or posting recipes.

qbrew-0.4.1/docs/book/handbook-reference.html0000644000175100017510000003457211014464530020100 0ustar daviddavid Chapter 2. QBrew Reference

Chapter 2. QBrew Reference

The Menus

The File Menu

File->New (Ctrl+N)

This creates a new empty recipe. If there is a current recipe with unsaved changes the user is given a chance to save it.

File->Open (Ctrl+O)

This opens an existing recipe. Use the Open dialog to select the recipe you wish to open.

File->Open Recent

This is a shortcut to open recently saved recipes. This item opens a list with several of the most recently saved recipes to choose from.

File->Save (Ctrl+S)

Saves the current recipe. If this is the first time the recipe has been saved, the Save As dialog (described next) will be shown.

File->Save As...

Saves the recipe under a new file name. A Save As dialog will be shown to select the name of the file to save to.

File->Export...

Exports the recipe to a different format. This command will display the Export dialog, which is very similar to the Save As dialog.

File->Print Preview...

Display a print preview dialog. This dialog can be used to preview the recipe before printing.

File->Print... (Ctrl+P)

This prints the recipe. Opens a print dialog allowing the user to specify where and how to print.

File->Quit(Ctrl+Q)

Quits QBrew. If you have unsaved recipes, you will be prompted to save them.

The Tools Menu

Tools->Alcohol Percentage...

Brings up the Alchohol Percentage tool. This is used to calculate alcohol percentage by weight or volume, from measured specific gravity readins.

Tools->Hydrometer Correction...

Brings up the Hydrometer Correction tool. This is used to correct hydrometer readings taken at temperatures other than the calibrated temperature.

Tools->Database Editor...

Brings up the Database Editor tool. This is used to edit the database of ingredients and styles.

The Options Menu

Options->Main Toolbar

Toggle the main toolbar on and off. When unchecked the toolbar is hidden.

Options->Statusbar

Toggle the status bar on and off. When unchecked the status bar is hidden.

Options->Configure...

This configures settings for QBrew. A dialog is shown to configure general, recipe and calculation related settings.

The Help Menu

Help->Contents (F1)

Opens up a help window and displays this document.

Help->Primer

Opens up a help window and displays a brewing primer.

Help->What's This? (Shift-F1)

Brings up a cursor you can use to find out what various controls and objects in the application do. Clicking on an item within QBrew will pop up a small window with help on that item.

Help->About...

Displays copyright and other basic information about QBrew

qbrew-0.4.1/docs/book/handbook-copyright.html0000644000175100017510000001007711014464643020151 0ustar daviddavid Copyright

Copyright

Copyright © 1999 - 2008 David Johnson, All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This product contains software written by Abe Kabakoff <abe_kabakoff@gmx.de>, and Michal Palczewski <michalp@gmail.com>

qbrew-0.4.1/docs/book/handbook-credits.html0000644000175100017510000001124711014464632017574 0ustar daviddavid Appendix A. Credits

Appendix A. Credits

Table of Contents

Helpful Folk
Copyright

Helpful Folk

I would like the thank the following people for their outstanding help with the creation of QBrew...

  • ...Abe Kabakoff for contributing several formulae and the Correction tool.

  • ...Michal Palczewski for contributing bug fixes and the Alcohol tool.

  • ...Lee Henderson, Rob Hudson, and Kevin Pullin for contributing bug fixes.

  • ...Tobias Toedter for German translation work.

  • ...Stephen Lowrie for a lot of rote work with the qbrewdata file, as well as major portions of the brewing primer.

  • ...the various maintainers for QBrew packages.

  • ...The numerous users who have written me and kept my spirits up.

qbrew-0.4.1/win/0000755000175100017510000000000011016417435012375 5ustar daviddavidqbrew-0.4.1/win/qbrew.nsi0000755000175100017510000001010311016417220014216 0ustar daviddavid; QBrew Win32 Installer Script ; Requires NSIS 2.0 and above ; http://nsis.sf.net !define PRODUCT "QBrew" !define VERSION "0.4.1" !define PUBLISHER "Usermode.org" !define WEB_SITE "http://www.usermode.org" !include "MUI.nsh" ;---------------------------------------- ; Configuration ; General Name "QBrew" OutFile "qbrew-install.exe" ; Folder-selection page InstallDir "$PROGRAMFILES\${PRODUCT}" ; Remember install folder InstallDirRegKey HKCU "Software\${PRODUCT}" "" ; don't bother with verification or compression CRCCheck off SetCompress off ;---------------------------------------- ;Interface Settings !define MUI_ABORTWARNING ;-------------------------------- ;Interface Configuration !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\orange-install.ico" !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico" !define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\orange.bmp" !define MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\orange-uninstall.bmp" ;---------------------------------------- ; Modern UI Configuration !insertmacro MUI_PAGE_WELCOME ;!insertmacro MUI_PAGE_LICENSE ;!insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES ;!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\doc\handbook-index.html" !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ;------------------------------------------ ; Languages !insertmacro MUI_LANGUAGE "English" ;------------------------------------------ ;Installer Sections ; Required, installs program files Section "QBrew Program" SecCore SectionIn RO SetShellVarContext all ; modifies 'all users' area of start menu SetOverwrite on SetOutPath $INSTDIR ; Files to get File /r "C:\qbrew\*" File "${NSISDIR}\Contrib\UIs\modern.exe" WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\QBrew" DisplayName "QBrew (remove only)" WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\QBrew" UninstallString "$INSTDIR\Uninstall.exe" WriteRegStr HKEY_LOCAL_MACHINE "Software\usermode\QBrew" "InstallDir" $INSTDIR WriteRegStr HKEY_LOCAL_MACHINE "Software\usermode\QBrew" "Version" "${VERSION}" WriteUninstaller "$INSTDIR\Uninstall.exe" SectionEnd ; Required, start menu shortcuts Section -StartMenu SetShellVarContext all CreateDirectory "$SMPROGRAMS\${PRODUCT}" IfFileExists "$SMPROGRAMS\${PRODUCT}" createIcons SetShellVarContext current CreateDirectory "$SMPROGRAMS\${PRODUCT}" createIcons: CreateShortcut "$SMPROGRAMS\${PRODUCT}\QBrew.lnk" "$INSTDIR\qbrew.exe" ;"" $INSTDIR\qbrew.ico CreateShortCut "$SMPROGRAMS\${PRODUCT}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" CreateShortCut "$SMPROGRAMS\${PRODUCT}\QBrew Handbook.lnk" "$INSTDIR\doc\handbook-index.html" CreateShortcut "$DESKTOP\QBrew.lnk" "$INSTDIR\qbrew.exe" ;"" $INSTDIR\qbrew.ico SectionEnd ;------------------------------------------ ;Descriptions ;!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN ;!insertmacro MUI_DESCRIPTION_TEXT ${SecCore} "QBrew program files. (Required for QBrew to run)" ;!insertmacro MUI_DESCRIPTION_TEXT ${SecDesktop} "Select to add a link to QBrew on your desktop." ;!insertmacro MUI_FUNCTION_DESCRIPTION_END ;------------------------------------------ ;Uninstaller Section Section "Uninstall" SetShellVarContext all Delete "$INSTDIR\modern.exe" Delete "$INSTDIR\Uninstall.exe" RMDir /r "$SMPROGRAMS\${PRODUCT}" Delete "$DESKTOP\QBrew.lnk" RMDir /r "$INSTDIR\doc" Delete "$INSTDIR\LICENSE" Delete "$INSTDIR\README" Delete "$INSTDIR\qbrew.exe" Delete "$INSTDIR\qbrewdata" Delete "$INSTDIR\splash.png" Delete "$INSTDIR\qbrew.ico" Delete "$INSTDIR\mingwm10.dll" Delete "$INSTDIR\QtCore4.dll" Delete "$INSTDIR\QtGui4.dll" RMDir "$INSTDIR" DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\QBrew" DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\usermode\QBrew" SectionEnd ;eof qbrew-0.4.1/win/qbrew.ico0000644000175100017510000000706611016417220014212 0ustar daviddavidh& ( @X#fL>lǿSy0t[nchJq^O,lS6zc~Iy\Bo:~gZ'iPSd4v_@~h/oXDtkL|+pVZHvMtpa;i$iN)lQ4t\5y`1v^*nT-nU.rZ7v_?nN~T{<~i%gM(jR-qX9}eJ{&gO+lU/qW3x_=jX%iP)lS+nV@l?l'kQ/qY0u]5w`5yb@oEs`RRz%hN&iO&hP)kR*oU,oV,qV-pW2v_4x`\@mBp^M}M~#fM$gM%hO%iO&iP'jP'jQ'jR*mT*nU+nT+mU+nU-mU+oV-oVc.qY3w_4w`6v_4y`5z`9}f:~h=k?lN~Iy,D@h0?n`n$p_t=A1^6Ww}@nnne\x[sGaH%P|ivdQliR[T rp3nob{QNn"ZluC(BIk[*)#+MV /cJ:`FZ?]g'j -8Y5vLn~\Kp\2SJ X[Uz Oy;fqmKm! wN[4o79l&( @X!dIwnIqBiĻN~jdo_}Fv[(mT2rZHyAp5x^6|d:~h?lLvtRVy'jO8yd=}f"eL-pX0t\d;zdk"hL*nR2w_JxOYg8|c@lGwrba#fK%iO(hO*kS+nT0rX>kDoBrEtFsGuHwJ{K|iuM}X~pRTV$fL'kQ,pV,rX3tZ1t^4w_5x`7zc9}fqAk@nAnnCqBphGuKzQ^VW(kS+oT0rZ0tZ8|d;h<iFofdHwOv_\[Z%hN'iP.qY1qY/s[1s[1u\7xc9zc;gAj>lmlHpfbcPPZ\RU!eK"dJ"fK#gM#fNǾ#hM&hN&jO(jP)kR)lS*mS*nT+nU-mT,mU,nV-oW.nV-qW.rX.rZ/rY1u]3t\2t]2v^ss3w^p5w`5y_o7{c{7{d:{c9~e9~gkkk;~i;iD[:ư=I_-i5ntd@L]gltϕRs:jh2`Ҏ<9獺OC2S'_r̔g>I$UM8STǖ:Axx?5sq[RU9 /=ϑjIW9oߵZaAlbbR׹٧UU3V5EuU4R2j_d7mTU=9oT[w/n~=UbJ_TIYxH:NtᚱS]j:T.!3gilJx݁~gU,4_(O/lc Ѝgx9azg3xJ:?5)T2_vcRg:?1%c:hzjhwƂlPmJg:UA@qET+=g44\Tgxj\$2<.ޡB)I\j@g,}2dqm;R4or3dA6*:fjvρRR" -'gIjrr.{πG)ǘڅSX+p5,LOO ?ق- O- -|Xqbrew-0.4.1/win/icon.rc0000755000175100017510000000005611016417220013647 0ustar daviddavidID_ICON ICON DISCARDABLE "qbrew.ico" qbrew-0.4.1/pics/0000755000175100017510000000000011016417406012534 5ustar daviddavidqbrew-0.4.1/pics/icons/0000755000175100017510000000000011016417412013644 5ustar daviddavidqbrew-0.4.1/pics/icons/fileprint.png0000644000175100017510000000257411016417221016354 0ustar daviddavidPNG  IHDRw=gAMA abKGD pHYs  @AtIME 4":IDATx]hW3;3Y|i<4-6BGDJVHP4~74E >هJb+5!ZJH(!hƺk&ٹ!!JC/8ߙs.lXLmذAئu W\ tTbƚ 8C;ڛ̝adT.S\N9 5swDض\ًjKXU-&滠=߇(,PHO3`^ӺJ܊25?",('=gT=x75A95 Z(]s.r ى hoNQ̣ 44 %˃HEJ: [ ֬RBC_,AJRHAC!io.^`,[[[CUUUΉ'8tO#du,^>(d,B@ tg!zz/@*+ L&eG1q'H$B,b[k9 qk(S+pHjXLuwwc� RR(LOO.A8.\`ӦM466RUUE8uࡑJ4MBBt]qRAv d$RJZZZX~=b6H*`F(RJ,4MA:Fg,~L,ʀbop1FTVV" B,4M۶ijj"qM>mxիW^j`ֲvZ***(dB`6d)%cccضM&u]6n 5~D.`444P]]M$0 B}\r)R###{|We bqP(V-[\ew^4M\e||I>Gghb %͛/]vm V^^eY>}1fpU%'l&˺gn@\3;YIENDB`qbrew-0.4.1/pics/icons/contexthelp.png0000644000175100017510000000124311016417221016705 0ustar daviddavidPNG  IHDRw=bKGD pHYs  d_tIME0Wqwn0IDATxNTAoQ11qeo`tҸ %9| WЕP0.Do3h'6"Yގ'M`,r4y]lao$_;mx l, jY`';7 )F3{ɚ"Y<} JtQ4JojD_+mx Ml賺I{⏅8gPYܑjή~z,Nw1r0_ >-Vȱ8Hb9݃ Ez]e̗*pGr#9j0"#5XEzei<'2#Ԕ!a6SH-X݈!MJRźl)4=mx3jc_fJnJo704`WylB=7k}"f5|n<.H}":KBiVX;-I諭Hĭlߤ Nċu" ~{Ql2D$Xf1@DD6RI04Kh JÌ860Tc yಈċ=Bd!")HoiCDG s* 0Kv)>@U13TodNDvlQ4'E{tǀ @\ 䴈\grBЯģ(淗}^BD&LdU%"Tt*12tCD8I2 M5Ӏ$gUVQ0.P$Ƭ ~`8i`\ထG@6 YbG[TtD,,bXosZ4DJ@)4#w[|p#A q.aY҃ 3 5%S>_Q7d޺a0sbKH<|&Oxtj3Կv..j\7o\+ff=s {]:A` Q~UzA^gaYfg3s`BsOi|3r8l#eF}sLUO"br*9h_lV.p*,ԦYL~0{o@BXU`^DKks=e&N6[}Mw,`t, zJSC6߾.03pI 0LڹDIn; PLf2@?s<"$y{ .-IENDB`qbrew-0.4.1/pics/icons/filesave.png0000644000175100017510000000112111016417221016141 0ustar daviddavidPNG  IHDRw=bKGD pHYs  ~tIME 6:HyIDATx1AMF҄ x&*S6V~,I%"$E b}U:FHdYl2;|طaQPJɮn4G-X,| ,PJIL&)O||Q!" C ,ob7|~'wn)9sbO ;QǼ h1k7l=Dq߾$x~ $8?;|IDATx}V]l\Gνڱqjgc%*q")V") (T U@P[%Ve秭RƵk'vכ{g][69Ҽo99gnvi!޸O((w w  pBsiGth`` 300>[[[** rffZ(rBͽ7m Ξ(t.y/aH)H$'J)!dasŅ"ѣw%JV,+ʣTBgt:m89IĕJRo>} {߿NJMOO/ /OEݯnڅ[}!<0!9cR KKKaO<,788ؾ{n8dbb"w̙ǟ!]ǭ:::sN()"I*=&x]Lg*jj+W,ܾ~|_.΅hlF`uI85١w9:ga6m_\\\Y]]ٟy ?\@pWv?Je,JԦ//5 m7;p=Ij:u*UeqW+G8]H}l/5= +H|v} 2 WJ95aup;Ԇa( +WUg~<+k\ K)X?|Ec)n)kc7Z.„NJI!h(S*dR^+>{kx=VY-WE<lmns2Vqar,c0ƚ6Tdb4??_3Ÿ.x᧺Q2/e*R/P 8cp! C!(G}xBAr/k#?RGn̠dRliwK3 u``v:y{PA@7k5aH3Lc>No1{-na&r(\\%p&]G}HB!fsN<#:/~sbJ{131U-E!`Zgq<9cR~?̯`jvX[^>{8}_RJ=\)h1@H:EQG'" RB)Bc>q((n2jJ)_kMsfew0+KsZkιXkM1F[ks&I6P0HB4!D+xXZl^?ҷ'Ϣ16IXkq,8IhcZ_-mRIk$<$wj c6ZD)%):RseC+9}]]]nǎq&atR)̚hLA=c AZ_ 8P_c={,!Dtt|G,v;X=@9':Ṉ>p0W?2>5`8d:D&lWPx?b-v[ ]؜IENDB`qbrew-0.4.1/pics/icons/editcut.png0000644000175100017510000000162411016417221016014 0ustar daviddavidPNG  IHDRw=gAMA abKGD~ pHYs  ~tIME  8XpIDATxkSAsmkXJwJAoP"(*FDtBuSE [Dp'.j1;ԸpXL{3I丘m& ̙99w悿h4 #g=뚤yjvGĽ'#nyrcZ(rK5DF{zqG^u#0lZŮ@!#'of!Q@30(U;\c?00R/ƁCRs;+Fl̽x O?"fh9#aӓ HK}aݠv/?+f|RMSo^,]WOxrllow،-Z F .M8nˍ8v\.aBF7%IOoTmNCa[ 42)N\ץqu%|CP9 d>U9Z  "|ʴs>SG%ZOjV,z40l Z5rk[(V";Iq0fO}DP75B[7nO9&9p058-H$K`v\u8Fx&em( R~ Wd2$, Hֳ%lܸbì JTj -a*$[a]#w׌z` ڹ}ͮ̈Sj&?Ť tWr(ISKM5IENDB`qbrew-0.4.1/pics/icons/forward.png0000644000175100017510000000314411016417221016016 0ustar daviddavidPNG  IHDRw=bKGD pHYs  d_tIME o(IDATx}mh\YޙL%NtnBa -BeJAAmv!\ V I16N0s=/~hý><<xxP %@#<`/t*u]q0\Zi<;RT* b1VUجVZ)< Cu[ccc|X@_eW/V.(J8\a !y4@ȉǏp]}Vsss\PWSB8D)ezX:ǵk}"Ϝ9s(NJRzÍ 'fff>g|  /ё#G .x]1N81hϗgff6H6X7Ig?˗?M)I1J)N`wGFFd2ݼsΣB@2 rֱ7ۃ&K.]+W>ORJ!&''9+JPf?ZqM$2~U|o^Vkq}Cdϝ;WPJV [o൓/>BZǩZk2>>qZ0 08'xV*%F b*^ٗE6/qV >K>9~u]+ ^)@z#*x1PZk"o X)RSo8g͛11j`eeQMA'z0w.Z qXn` m :<&!(G8jU.G 8!%x 6ЋќI,//_vkػ<_,u(ԁ8u K|mmZkN)%1Q58j<xH:7w]ZǔRQ'r v?R kW«\eYNY*{ajv7RJ=ι0ss&<RjBBHZ5` ˫ؚoBVJc1JiD)%ڕ:0 WcaƤb<2 ][u8;1-uF:RϗTsFJWױҫ !x#/c>7R@]bui{ejQn; [DCNĘ&".Q`yjm;"M{4@<)R% W ^7z@}b+zeqmg[xL?\)b灀W=P'NxL;peDڭ##&KCC5YBJ.#FFF9` (àje/_C|n, 畿1|iMf(F%2 CRP<%? )|HVNF~?걠uSN@*(ͩߦaak7~2O~%]75MI)͋_:OЁk H*6ֱW?~>;#,Op_.))I\I) -Xdmj`]U4\+2k,tIG}>_faͫ---X8}ܹs VY0YHsceRe6t]Ϻ\Tyyy" n;kX]v|>>?;NϻTqQj8YȦ}'HӬ%fp˧|I.].ivu`t:2Hw޽K©h!-2\x5fgTg~k{u;w5MKy<t: ,//7jzB&1 ('N\~yd#f#(N}<}M5rXfy::nUUzzzebbh4g(Y5lmmu4_j\m}{;ж/p~}ի JX$bmAzѢHyG_){f߁p󷣣\|[nv9vVP(F@A 0r@ zy'YO&Y}C:WQQD"ʕ+ƍ>||>O Xb*q8uA{ww2H$svN4ػw/h06@1Lx6==휝:UUTVV3---W, B3g066FYY!|Wa`xԢ(4-xTR^^^ B2vXVЄ~Mu!RJ#sLUR:R6뺎afUUUFQjZEUvZB||>O:~7s0!xKJviʫB_1<E,l6NK.*? ď;w@ @lٱL@ivsƊ H/kּ/J].6KvMVd;1v,OǾ$@\Xn(qasp&;0^ < kSCpQ`OtꅱX7n&5#D;ىv$6LFc&AMOqG?СC. a !6opq1bcԴHM18fp/%b1-F 0<+s=zАsN.)GqШИA4F_@%"K0"z{{fN Bt< 5 fTYЄ`щZ˺I#F(:NHQ50!4G2%=|@NN@RKEioGJH"i!|/TȠ9,7#3g)((_1O[gϰ(-ܙFC_.V;Shml YfNeee֮]K~<ΎϿ$۱P5Ax,F 9Y%J\8[ؿ!+,azyyŇ&='H8?KFmu%Bp'ގ1&uR"$I{GJW7NeLDNVWRSi/GzVh ^ZT`I, 5}l۶@ m/ǖeh=8n/8hrKi}yvޅ$Iex<`ʕx^< P^^L_+3MⰪrsΓ(H.KȲLssDWi\RR23iv8pe[xX=XQӧsynUWWk15|IENDB`qbrew-0.4.1/pics/icons/down.png0000644000175100017510000000315111016417221015317 0ustar daviddavidPNG  IHDRw=bKGD pHYs  d_tIME /QIDATx}Kl\Wƿ󸏹㙹3cOة[۴Hƈ:<4jDRD%D"Q7,@la@d"+"U R"l4M[Ox<{8[__zT.;w44pJ)ٶ=;??q=-hvdd$788( ++++[BxG)RsE!Y `?`||G>aV+=0`GM|ǿ.xǜc Q5ٳg+.\j 0C5ƀsN |c&T<űU,k@k )}? poU @D|ssq֭婩 Ba0๞ ~V*bmdܙk&@kmF0 {'&&˱sZʕ+nskiav>{-!cRjGu]bYVv̉'RF\ti%!5r]$eP$2J)EBX'vByvX,߼ysKf2NY"jW8q|tvQGGǮRƶm!!xIZg& `·rXcXd_?/|_|~(Kz{_gXݞT*_?-<K&̙3W}gELwOZJi c`1BQJa!`J)Ο?oO=}&JwjިԲ,q8bւ cl9%!D*Wa+!`rrlbX6Bmۆc>p@EK(BիWrW4Z -JRcBXZH)%޹MR!!6!d8"^TX:VZ\vkǎ cJ).l PkݔR B{*ȑ#}Ƙ/ lL&RKRy=k[۶cRJZ)0d ,--U߉%+x !ĵP`<3>H 0s߮`hh`T[$ CR dFbB$ "'<}fiOMBI'&4 wcQFGh\G n 3-ry"gVU~ER + 5Ͳy)#:}HJ]]q4r3)G2 166Lrz4Kd&dY7L vZ}1v|5.#QD>IY (ΐ8GutoCKX/-kuuuUVVB 3/?":sȉu&5f-ZBSLn)666@,$~,cBn!u N1^reWʕ6:ӌE×T_x&PȳóN2+ՄIENDB`qbrew-0.4.1/pics/icons/exit.png0000644000175100017510000000226011016417221015321 0ustar daviddavidPNG  IHDRĴl;gAMA abKGD pHYs  ~tIME +0-IDATx?hGƿy3;gnvۓ|kDHPp@)uvܨRK k!)B0DP H!,;nwSn~00;o7{k|.fScowظX*a0 (Me?6,.l2 E d(( ]^ Lӄi899 9dbeYvq*Rn`뽼wGyc2p8l i0w8fiy"MSjZ^@{j(MSZ]]ݽ{9s+VvOӴY$$:NV$ qLi6777]K`6.`mm /--Q$EQ(nvH$I"$J]4Mlnnj7661aXZ hccTիaR|qq(RZFA0 E|h-..8 ֚lmӬ$s3!D2 I$$ x<]-9@K(0 VVV @p8{||m6P7011 xǏna0<<Ba7J] tt::affxod"CεjIVmxIݻwDx!C&ǃir@Yn,r|ssm>},126F혝xH4kL&(7@@bccUTU峇@ ay^ݴ~ V=wv !KKKD"PUiapp&@4Z^zzzF 'I_ZXٔidS)fhIu>,?;;ϝ;w| XE6Ul]t1/ >fzkұ(?3owqNs1KGG>]瀌sX&ySSSo|J9BR3%phZsgbnw*(dcXf=\'ii 4MWyH;Es_K蒕K%rf+a'EFQ}\*::)_ BYEp[5hj[g dlj(s]zN( nŒPR LrttD.|?JEEJ=d*E2אzDt^ӕuGK{@I}!UKTm-kX*}Pd}Bmmu@-47J@JڞIENDB`qbrew-0.4.1/pics/icons/editcopy.png0000644000175100017510000000252011016417221016167 0ustar daviddavidPNG  IHDRĴl;gAMA abKGDC pHYs  tIME  5 `wIDATx[Lg~E"de0!^ݒb4Wj.ƫ,$K46 4!V Lkۅ($Oދyyo ɤ6KUFhAbHl`0s,e9Kfd*TZZn]]ܹsp8LQQCCCׯfx|9@yw444(׮]Sׯĉ9(7ɣjڲ/\` N3>>N"`mr208 D"c6rQ dQI߶rU,B#j) fF(ʁ+X,S: C; HƢr\{EQܫREAXXXp`qI?9s7oGEE9<=)͞ twwcZ9}4&i] EFcc#^0ణDwb#&&&&H&:NF3I2%O確۷nQRRBMM (2<<|$HP/u.q໳gyZdA*mV\*Xo76E*BeΟ;G:b7mf쩫RX H%cFz=]LNNrt2PP(ǏrK`EQ>/}oxxOi4Y0$@yfl6TTT`6^ D"nms̳n7 {ЭQI|L{H"NɎTUU1??ÇWIC͇$>?C2!05y#1F\̄pWl6)(( ,+[סvt:fh>w'(0dwJRxk@^YպH #Y'3(bӥK$ Ih4h4֭[bb륺P(hF7V``ju:]l@ee%x9|^/Z)9 j5[nt:t:%IBRRpo)|ldY&NdE jK~_K@T^ 5Wmw,_V-1XPIENDB`qbrew-0.4.1/pics/icons/find.png0000644000175100017510000000272611016417221015277 0ustar daviddavidPNG  IHDRĴl;gAMA abKGDC pHYs ,tIME  SIDATxu]lUwv*-[hVO(@$bLF4Db0HX+mt HI,JEeLwgvfvj$&{͛_xp]UUرo<ܨiii ގBsOl*u]'@/P Tze˖٩ z{{4 ) O<|xj}1B)’.iGRQA^Opxq3gV%@mmRذIX /6+*WDlf={`6T~T˲v5}3YbrqRI̩+1cff8Ǚx<!t~Ŝ½iMh8g,'%ұ(XU' _ TUU Ps,)/fd3FX,Lo_ǫ#Չ(ljڵk|}J抑$ 9,fOמɴ:*7c۶uСÁ@\J* -^|:ñ>OwWRJJԅ7F.u!aH$(FX8RJ.!329wxq( u\R }S/(۶RRWWG{{/Y/+￟}cNzzz_X9x`8 q~S@)/%%%4M p^\9:y7imnξ |x0MMMTVVsǏ~ @q4`E㫚-$<5EDu˚}&v 7[k7K]H\F.BEhE@LQ"t"]Qǧ8pw\'v]hxT\,[@o vX]]e}}*9'ϳ+n7BeD^GB\A%ˏ?K,Go UUQU5׫FqG P TU*(J1@v/%S gt<Nֹ.IV~Y~T7W7~Grg0W]N4!XoHc6X,D"TA&Ŋ܈#Ph4J8& bbo+++g7t 7 TUeyyh4 Ǚ.@s...8??'L@<K4fIX,  ;E:2 |>RaZBprr@Z ^x[ j49>>>SSSL&F<X콚t ohL&)--5$Ir! JfIRlll)$9BimmE&%L&ryybY Bt:哻ޥSPP8$`49;;Ce fqq`t=ڸmEEEQCCCx^<+++D"ltuuR&WTTxDSSVUb1.?\w k' >/T֙۫IENDB`qbrew-0.4.1/pics/icons/configure.png0000644000175100017510000000251411016417221016333 0ustar daviddavidPNG  IHDR!-bKGDC pHYs  tIME5IDATx]LW`qHFs1`q\f&bev3f1:@La&& 4)_VhEJ;lم-Qܞ>sVv{`$ݳ. 0[M+Uw+V,\PZ1O"`$ˊA@UU p"tnMHJJ>/RJ}v[1_22i&,ˣe+DLLLPg4(U꺎j~w:꘰Z =Ϭ^=zT !x(+$6m[>n HPb=#YQbuioڊtQV~?=,$dKKH%NKq5MR_F Q1\sgӑtA(ŝz =%-'DR_ꨩ\(I/hmm'==q'򷥡SA(c|`_/MvV3@ϝ-aC@mEaa!v'Oܹc"NvP(^?H2k4ur|WɭFccHQsnn?t_pEvJKK)** @.LmxkVn'Py`NpյH1[Ŵ9Dۏ$&&~͆ {O__ok39Nt[-unA#&奿PPP VZ+oϛ\G2#E.C8 oġɼ,Ex) xfxRvT݁1,uA59L^ L k*UjGM5#蛛ӨЉ˲X%qȐGxu yNL|$| ?_Z7{[~E\Ǝʝ{~B!wEkkk*|^yyRJ)5-)76~|~2L㱘rA|[+--- {ѯ|dO(U2K/s $[(wӧ6!`=̯sM+k(`XܖUU1M P:J!>w.}H6P(dX]]ecc@ @\& iEG8 ^_@AGGð1Mمn&?9ljaqW)׮]ѽz7E6T6 ף*i)%m;ڮK.#|&`,ˢp8iJJ%B+++5W%'"###D"M=W,,,P,d޽tuuN[[lhhD"!XDnn)hF:v$\ mc1x"Rr\55ETST,=h44_@i&EٱH"}MꀔeYQld\SQa")MoDQ4Mk4 h`y@~Q.dH=||džpq!Bܣ`N T5Nn$:>u|5^&i"x-#G~qr|OZgGCi?JU+Dk9|w݌F=^^Ǐ@p ̕=G#V[J6 )lG*Ogg'Of9|W [y"!iU`CJIYYnC-$I$IJv*B HH)BlKVUUQp8;<'kvwǜ9%+Sb mIߝ%X__G-qz{{1MjkkwlS[ `ppa ð @u/M rPhav;b[P@-wT ^l; 2Bk8 Ig  Lt'4M L'-7H6*4-=29~dN;'IENDB`qbrew-0.4.1/pics/icons/plus.png0000644000175100017510000000075511016417221015342 0ustar daviddavidPNG  IHDRj pHYs  tIME2YPLTE?XHeJdJeNh[tipĉъҍٍݎސ⑧咨蓩甪ꔫ땭otRNS &(,EKcghisbKGDHIDAT1r@`D>,in=^ nǕ:oIw$`Dd?9.r|}|?aEzdm-,ɐs-s#1dax+z$2듭vJd j;DhV;Zsno署 Po #mж?ފz_}IENDB`qbrew-0.4.1/pics/icons/up.png0000644000175100017510000000331411016417221014775 0ustar daviddavidPNG  IHDRw=gAMA abKGD pHYs  d_tIME IIDATx}[l\Gƿ9gwKbӦ!QQ6XA) A@ڔ%!(HJ<UBBiDUjD+c^h^9gfxnIKyi4} CUEY>07`㜯A?`^2'.۶Y.JRfr6 1Rʃ `BT*u~uxuttmϞ=t:ZMRt:K)ᜇA\re:|<~;vt !DTk1?7innVsB眄ah S*D"qܹsKw 8?o߾W]?!43,0!(Ld2O6Rܹ9J_x17]zS{3_g)_aD"`O ujZq…;t_#aL,p)Y#1~K#uضMlۦB2zY2t{zzږn߾]T{.س8ut?U񗌇r݅ Is߿'1F)e04RJ8C,X/"3<<) CU.gϮT{ƿ10/Lgǹ,Us1]xϼ_QJFJIc]L&;99:S?ͣūo/o. +e k\z6t!o/|h4ʢ(bb8* ~T\+m؉w1_C :fjf^ .yeY1FcFt>^|Or+`)7~::yS [e Fw~J݇`cN_}덃ffkllٲE6~׾ϡ˫moBv>`qPJ(L%d*4Bb6B`gM.DFzPDZDZ3m !YUBfCJ)8IJ,9's+9h0c۶8NĶm[`fYqSJa!1F(l;5IP̯U1!K)8!Ra0a-p>|EJyDJ!Fabo"R Q@!B#d1RZc̍ cAI)=cߔ#4wb @&37r0Ԥ:PJI60 z @)Bf !A|>ZZZTf. x̓@JYcZZR)o >`޽Ƙ}}}~8<2uqZO cQJ)Zk}֭[7nۋZm H$eYdcAa[}}}Sw3? ]v-!D̲,Rb1Zk#yݶm[ü{*'OljiinnnnbX,_,H)]c- /6sIENDB`qbrew-0.4.1/pics/icons/COPYING0000644000175100017510000000013411016417221014673 0ustar daviddavidThe images inside this directory are COMPLETELY FREE for commercial and non-commercial use. qbrew-0.4.1/pics/icons/filenew.png0000644000175100017510000000216711016417221016007 0ustar daviddavidPNG  IHDRw=bKGDC pHYs  #utIME!=IDATxOHw?٘VӘܶv =D ! [Ti(T۔\r4RȵTj[Zh6q]uafQJ 7~}}ga $]='Z4M&&&N6[[[ WGAN>"("JY K0O4Y2$!2dY<!H]˲e2Der]S5<!TweQ4MC46775a)x UϨjICC$bZaUg}r  [Q< ) b7@u\w쟪-Fǥ˯1C( /7+L8Dww aYB&#iOH1+&C,/ !5+ T*QVc1jfе!~(}-  ^vTwP9 Xb~NPx`h4J$6,6|6i>ꥠ.@8'GfD*qsx1a[x ޺>0#) bq1qsX6v`HWLғy8 Q >c/QU:2 |y T7Twv,!1AZ*o?hfP2rqkF-1MJX~厉Xgrtt7Ea<4#WȄm e.cD#ޡ&6=@( RoCȣP #p-Z +mDџAUEc<;s3|16gf0@k/@IUd*ܽ[t<|J:JKyJ"lysA4Ё_ Жh$j:6ZmS @,<³a @kݒNmW^strIENDB`qbrew-0.4.1/pics/icons/redo.png0000644000175100017510000000200411016417221015275 0ustar daviddavidPNG  IHDRĴl;gAMA abKGD pHYs mtIME 8,#bIDATxOhU3f3Ӹ.b'FmVk)j<DA!UCAЋ*x ԕZ1]3d7&;aٵ6{3"_|&$M!tNu$FҕU` JmqD0Y$,R@  \$tGb$916cyqR/=ʣ3<5vE]e@O % <=zwU,[f[H>16N^]˙1:ۉjxٴY7}@A膙51<z*-çG;isןw)[g-3{='&>iW hlJE@?5ӣNrRi#L} N KZFw7['Y6{{2`;uО4I5Ij^)J}CW<[@8fѝ^ѹQ&+fg)~Xx2:0kNJ9DpfZ(ef h"6El.P2 r Mvݡt|+Rd7r#qi(Ie4Eu03_x05 􈡉r(Hbءw]ߖRiY>(|]K&^ xW7˯ T|NcUļvOW]|;aIpjqvŰ"Ր`=b@ߓxJ{]UXY0a Uk',l &}OmPLUIENDB`qbrew-0.4.1/pics/icons/minus.png0000644000175100017510000000040211016417221015477 0ustar daviddavidPNG  IHDRĴl; pHYs  tIMEIbKGDIDAT8c``b fb620L/ <0OE,pֿd)aA  [|l앲fpk ֤ZpAa$;Gb*b0f:gCm%gC n(G -2&T?J:YCb9IENDB`qbrew-0.4.1/pics/qbrew.png0000644000175100017510000001044111016417221014355 0ustar daviddavidPNG  IHDR00WbKGD pHYs  tIME  rtEXtCommentCreated with The GIMPd%nIDATh͚i\Wu޷2==4mKlK!8@al,!E(0q") C"\TJ*lDM0x%[G3hIlb!9~wsϹOw}d(P^><woĻ xoĻozo ~O8qX)Yk.S>zL{~X~@&L&#lf~<އrF%|[k]ȤRZ2i @V.ד$~Y('EئHOr-Y֚KaI,Ru\{B˫k$IxG/ʷQ3dʤSMC̹QQD{k8h\YX~o/x驩oQ57xhc{(oe3C۷nJ)R(!B4w8瘚e|b 9Q0{JMzb#cs|GR)IfMžE(7_0PNvwwnx)|OBIy 'Ne{dR!]Xkp_ 3an,qkrb~c߻2Ji: lR(%==SxJq( PE|(vwae~:ƺ%}C[_|_5'S_9r~{k댌_==A%%RJR#MwoKKL]aiymXKQDZ{Z >.zܱY`89T\]o# xu"I<$6qR,ٴH.ESSȤS^Vߋsw:K%{<[_޳s{M^c-gSFXx+۵L:l<6ogJڒ%If;L:ԣ_k{PD|=Fޡ-]퍸: 6| QL=cp 9D/9ߧ_CJk*A'ZСTZ{[z/nߺ9-|# B'|2鐵2eV(U(0pa%=731=bD_9k sYZDdםr]+%׭TcCOoaCoPRaNd3iJL̰nmd3YcƠ!ц$IB$xϩ1xBy)RHT2TY7r, 䎁E5cZ);q[6dRbKkqMR6Ip2ItuRh)Yi81DH/fXc1Ҡl&'\!JY--YK+R ĚUWX[[=v neh6: 3~i(NPJbtC"I!/G/L^?#ec,Ftg10|a ņnZٹTU`a~"p~jQzũiQM~u$sO?qȺe\XiyddikzL\#ey|t*D A-5tvBg ) ⤑Ȟ#hmiap#&QĤHb!ӉsK4tkdk^jNZ##8&}|/`}d3I3ɭWQTfH$ĉZsc@*@{>16cڱTV"oE@8RaH=95|& ¦M32>ېұ^s1"am2]Rͼʙ BoVl4\ɜܙmIFI5S(RF:lNpʹaʕ6]à{ﺓ󿘚Z2ā=ClxM`H%XY]srO#?~# =)!%WJ {qk-NgS ۰1;s/ CҩFr4:ad}Fzrpϓl qB^RťucZ7P=&Iso͉SgF2ko3=~ XGֽŞ߳ߗ8!mZl(">/&'BoWCoac_Ͽ|s#cډkΎ B (h<63<6U,-01=S R7~Z%0F"cvnƩCikK/3qzHq>w9y,ZsA5ԣFצB\&gfĥWC[;o/<-qԣյ5VVָHoLt)y:;9as.Y/f3$IL[kN2̕+T'B}W?wnhk'.SAS!6 u7rft ]v B *{(ng_؇RzThDSYYOl^1 )\}L*%@^#͐hM{ܺg7_`ㄥNL;o=)Iiu 9ql '_?rᙛqCCh7>˥g&W R $8NXThm!e4f(W*d2)Ξe\޻!Ō^k ffj >đ _Y?rp{w(I !IgRNLQ(`erzӳsD=ifpzd4VwSJr{~cy#QQՕtr0Z<)7щ O+=Se}Wכ|եx9%v[&Ibzۙ4ezzZ2_Z_qY^.ϻ@&O6Б+;yӕ$B*A'Y[^Lt*ΖzRj훖k/>7cpq~fv9IENDB`qbrew-0.4.1/pics/splash.png0000644000175100017510000013004211016417221014527 0ustar daviddavidPNG  IHDR@O*<sRGB pHYs  tIME .tEXtCommentCreated with The GIMPd%n IDATxgeuR't72HD I&&)%+`R31kfRU#[t4g?Sn:/ÏCº\ DCR5OȺdYeiqi3SS3!7yی+ eUy2ql=~LMf6fmRr_Omos,ڴH$yԎm̬Iz\rN) `ӝٴi:_%/V^J#It(SN^f:_ֶiina[UDt I%D$Zh:__6l&lNYd7 n=m#h) 9h0Um)z;:ms&h4??S0gBw7QFT3u`^'l:eQ8aؿ࣏?g!NNNlټ3Oy;v8֣ufz}Զfz65mԜ <'50SRjXl?w}oGnW^n;ކ믹򚫯x_صGUOcSltYg\nۺe?viռb ?.ڰiGmۭɉ>^^Ôr4KKhd`+tY(;zd8C ?_jϾ_ۯw%\^}u='>|g{}=缸3.~-:Jߨvw1Ɂc Turpv>pu)SrN+JؾmS9b6+(چ降1Pqš@@"j Dd"!BGQH**ݜ#,,.dwԭۨa߿_$$KO?3ComG횞zG[_Ūցp[Yc۔8066DG8ťpبt[Z^_\\Yi\u1v:9eMb9甲0əxWK[8\Ա#)Bfn  F!GmՁ!"oNOY7|G/)[RJ=wfzj {pwt:P`" KvnۼiӦM334{ťNvA3ɃAoԶj([3Ͻ4TuzZ-4iȨiGͨɩU=ga≺Á^.V˖n8BPqb"F0sF$qw,9;92Գ#7v,A>8{c֛UoC0ܳ+afSwF)d2Uѐ10HdjiDHn5'Qb$bF"͒18p(mjUzӦ ܸ;8 "VBEDFY9G#FCtGpg@F2rspDpf`0^'<5=qw]r.7?Sڝw}(Eng]۷rg~/.6MsE~S,o}<< m;w^slXJ{g?`rbb-矷.<Ň~|=s0flڴθʷoټ }>=s MIU!8=5yW7w޽g_Uf8So3N? `3KYGMm;ZU 8pX8"$u5b "b\*fNU݁8 v:b+MnH2lnMzi] (1g  > [ivc)]Gj>ı{;;w/,+xl'vQڮ.={/O=k/~g /._6^_ыߺgyzk({_ǎlj?/sgGMs9/,.-,.=SOÇJoy®op4MIR,DHZΥ;!  #X AEU-%E0 !:8; 8#8b QDCM6Qw7n28"[>07r+YPBM@ fY"gsw@ LHD.b -206)7Nݝ ]vVmNߜvswƳ}EZ9/?3<ݶ.?r߾ou@88o~0zn.a_uj-Ӈ}~;Zo/;.KK+횝[zqȌoZ95i!fc$D{YC}~g>㜳K.?쑻.hg'?ޛ7_}~w G_ f翸ڑ^[p#s_l$u\U<0@DHHM`"DĄ`&YE )s57]9`"b$!D&&Ct@Ϫ"nC" ݸZ"#I(2Ev,&IKh;" 2mƌ1` 1)fb^W՛vڳz+P;w#8f_2^6xo:>.Aye_O~en~C~y;<>Ï{|D>r뉷_3fƈPX"$$(DuHj֌QwC$s/!3*pXJH rW͝:cL$fUU&+Cu;uD5эڜjQ4Q;9"ՁKl AHDHI<R ( F@@f@&`Ȉ[KkݽBoa4jFnJ`nE42 V UU##`@~ #BfB͚Q.h$́fyrpqn} +5]6Yݿ`n As+)bFK(#00"S0S(iD&bB 4u4 sKK@LO:^ /nc{Qu,^*s_Hԭq}Ư}?Ūbnc%#}쉧_ghNٲy rX{kM]s,n񺓻6oSr۩`03 9 &J90!k6#@э6jF&"PpL"`` 5b09zjmE_s߶)& 4f8rI:&yDb ]ZMY   U@L cňf.N"(Ʃ"Z}j'ҋݵͫ 3K? |JݻaK߸;ʟkDg~j vǷڹkOM;ξs87aKȎW?ئK|7s>c/yMW s=k#bƙ n%beOE*HL`Ds5mL SHL[pcX3GFUJ6-RC4"UrD7O#m6&MIrVC:ب-/,-./-Q_7L:V6&8 Q 'T 0PW D(NjRPR2Rp׬ '3SR&/c' jv$Oj؇akL,d)Okξ~Yཿۓʗ^{MjÖ9iΥ\&f§7CQ&Tw@BpVVnnQMNF**!p݉:551cneoG6ԝ8RTW\V`}py7,0ѝظqc)X6D U`BDps fsA8d7bC )1V LnBp} iqmɞ;Ũw9W\vQ~fzd!%6 /Wk} Gs/\wk;S -7ONLqo!-+cdR-:#!8xvUt.- 5 ;r4%,#&nhUe Đ1ƀ*ΌDYeqaۦ̈́$wPו$;V ;!0"!AT#*ÀnQ@pϭ4O%|]t[y-[6;Ǎuo{o;L~~5_Ʒ8Cmvo|CEGݭ< ?}vGpc.>y/Η:k`$v'yw&B1[#"YR" Hj`>nFp"DS a|sI4@O2ʩIfDD 9"1NDdwCwGDđ#5IDP-3m4Q+++++2 A ͋+V13dK{؉0Z* Dީ'q̭ﻩ~װ5KvWM/wkx?~X[O(=ӏ?oӟjֽ,Nr=3N=DZ>8~h*[wӋ.{O|?BzʖO^7NOm0c^J$;@N +: :h4JC "d"s75IYsąa͠mzQQQb S #sdsRqwDBb d&Tgj39Nd59ݝݰyrf橙7Mn;}fNa$j9Yq:(12 %iۚ2RiU@b"crn*M.3w-.5W]~R#]5T >䉔s~={wzj7R:gK?~7y:vx)[ny1ٯ@#*n?eroyy7! Q ȔE91)qPfv3S(i!:dќQE9Śb#C<ݴBaQČLTX%P-:Z9p4F&͝Aĉ KwԨ@IĖi"z$wQc^ !OX:9Z ܴ NU5m_ǟZ21{n~MX?Չ[k|M'8TөY޲yWt}uW&>Wʟ۶rTLD NEjUR#Ҋ8SБǢphHH15N5 ZLV0# 1H0$t%k)] L0B f&'l9kL]JV+HTjHL ׺oz"~Xҋ.]׬?[ᄆIԪgfOݾv\|GEos;_ؽ<5%%Dag~;.{I5n IDATyٹmhffӶs/ɔ~;qW='?RWՃ?vՕo-~S]v?0+?0jZӢ@NR+y;Usm(zƒ2!fXpH궀@n%&()bnWTȕ%KHNdAH&KRbTs`LTIŵDЙSdLX5AA 8!@('5S"ЧDSMy橙k?/Q}?߿ߙy }{z-=0 d<JfZF ܁(Tkv@WW1f q,9H:[ @q6s3Gtw4If75bB&Da:G"t0st@#A-"ssWGQf31puqDgp11iGB VFecI5$ ;u[uyO/u[|asߵ{oin[؏^ˁ~DO'WKK0lfʈE\UEs[RNDCA88:*PQptMiTZ URl\|*J@rJ5v0sRҔ$5Y؉hFSpr3DDFr ޴jH׽hxp˭)5LhH@⮸f[K޶ivn_x'Nd]_()$!/.6M;3=owqSẊlv6m޳w¢[=I571&@՜921#! xJW͂g`&IsM^ Y]r63!uDFw)&E$&j14gb{ k}n=O^jKvo|\Q~,X~"t(|HOfUCxs@hMZj$"YLb&& DTfUEsѢn-Dc)Ip4$($݉1LY"e礡VTXbc#g@%p8DvSI jW!afv}v?;7cS۷m=wY 3pIͻum+~ND8rngrjb/D.:~f>r::ܕpaEسmr#uR$Y wPJ Ba8ִhU\ʤ1(Suwc :HvI`Tn`T-ѝW݊CͨUUueOKBD}q^4p46M+1[zݩ`p\]W;9M3*Nv[6nE( W rH# ؇@nh&.LAH53U7 %8Q0! cGU$1ƣfD]E ؁cr R1US35o[!DT 6Q*ٍƒFB f.&*(8xp .aBMmfS'&`º˒_"&喈WF]m}r U7bӴ8s֭Ua:-6 V$! D@)%"Y\&dzhj0X{?GR!*`<ªȁC$@ Y%in9X)kαScjwEb@ 3f f9y;Jn@PId&ZNUUS J3hЁ +nq݁ +&57*NUk۬j?(5C2.HH(Vv,8jbGD90a:ԛ7nZ{i~MA8-n D+2wɪRPp`B"^pG)s >X"#xjZIdjEƪfHF̌ܘDtpI*#u B$q9;@&$5Q5UUn52ݛtBwXųt:21QX \ݡ$QD-T*DDX32TJ2_9NtSj{}fΑdqb@?knf)AaZQxE8RXECCB3p5s'&9VV r "Tv5k8FBB D&CGbDtC$i3dn3:t Bit&"Ƭ ͹(\}LFwW8:jVxWA2 UEO#2&+ p\BEK~lȲZ {?x'Gm떏}_;{Y*"C'hžQ_n> VVzJ\j3E2jMMFC]CJUA1F:ys9bp N-DtD̝A4g)P-YGƁ#Qd&0GJm.*4)Vug!dsDHk䀱hVwX1gy#0d[5@"FDrpf>tC2M" THXt{XfӚ[Ee'ťSBY \WME0NgÆޠߦJVqZV3UuС= 8 u&P $掁9r ZLڸ ne}X3FI89E)ALX,s0= >swڽw?Ck6p4m_uۯ?_Xڲy/^Mymd3u3 Lhb*"EJsz<ĞB*CeA:+1ʌQq1+k9:"pWb@(aTT&/?R&t ?{'g)bqZB ϥ˹+%w5͚S֬9iIZWB"$v4f5ͧ64q؀Atf A y)a$q, Tծ˹(b7-(+PtFj&M+MPRoN]AvD͈VHhXh_]Z%W@"Rպ_Ll3.쎋~,:ٲ˫.P!JlB!P͓hNZ2 V|uZuvjRǚe:i'ĸ1ȬRm~| _y;v9?woƯ׿>@kt}_{~ݝ _7&VO>̇>Gϼڶy_zt7.p|rS~ǞxΞ~ٿI,W}J֊pF3yh=U=0":lX841`~٥urW`,EK(S.fO(`>@ ."ɀ5B@ॢuʹ97 JFS3w0 dvsg$"H%l!H":BiӍ&ļ=ƦiW\&@%x ) #9l:,\Y@ZzԴgOm?hٿ/ S8Z,o_wjku7㟸K>W1=sogOM]<7K"ݏOѯS[s~O׮^?VXO8Ϗ}e9tGbFa \UMX- b`JΖ;5=2D^愥/]nVD qd,d@j,Df5DRyiF`ƕi)Ym3[[0;!i85uGCMZdJ (j R@W05>P\B<}fs/& ɥmSM|wglWu8L|g1ٴw~cuM)? 8ctg8"꿞>my;|>n;o `$!nMT !#R*"k6t@YzS*)ّ84"  (P5z@HC6^*3+NPHU듖,LԐBj+CT %13XVLsMGkG)eKE6k_Z١q5 Utʗ%B/VHruI ^7>9K/S>O}|o}R,}K//ky}mw}'O\8.Bsygpܙ{7}GoysgO~-o?smo[t_)[?\Μ>u]w~xQ~⹷|;a/eݕd%(P"K VD@7/ٴj'16Ac&6J@+ufuG ǖXؽͭ*|> +n !Hl# bEjjbH$ Lk DqΛf\5# Nޜt2uwh(mHV7>@B!Lc@LR7 %H54PK7&MӨyjV7}aPmn&bqfÏ=\̬i;.>ݛ&>u}~mE_oݻv/]jM[ήN';_wj\w=w=}X>-o_yfVbx="O}?12ӧz_O'ZGu2 Nޕ;;GVu |"D2up' g-V'Є$ W2AN[u5416iWOR[7 jTI(A՜PVh؟2'2{"$Q Cq:Ȫ `6ښmF0򙧞Gg\xa:ܲz}kWVfR+VU5nFif{Lh]Uؐ<0NopqOx Ny[wTjɨvHiQD`?wfjЌB,h9z {=uYggpL3SV,4 reŁ9G;v%&(fjDXAH*ԩr&۳,g-1Q#ܚM gsթ[_ӓɔJmlrME\*i #1D1ɨ!ܧAHXuMonv̙d/)jRς +Bxrj_ٔTԁc)&1ew@$Gp QzOu[ct IDATPmk"9 5b8I8OؘEĈ́EyԄ7ڠϫd/|S"aZX/e6H^sOo .,X7sz`Q+N̑(M3'hԴMhTHm|~ӛ$vO=ؿ{mZ+VmYkV :1Whs߰7$.Ͳc D0WP"wl`V &J*ĈRA99 HOl۶Q*vj/o= A`{sڶzr1olllMsoą`snGEO,k=-HjDƗ/T}!d4ݎu-SVzc6!4R[)+= "qàLA8&4޾9HlɍADms)J `dY;-\v>fܑ0UTJ*Bñ "Aʔv2Būx:FRvATSCF [:'d&ZB"& F$՞[]!@JeU ?R4!H4G*ZjD|c{0(L2 [FE7X`ڶm/*1s9F1rs}a*E  {U ,jf@PSqpuL:  GEzs7@dUbVCdfDGrB6R l6mU3MaGmӤ2^ u]ߧՃý㨉MMgi6FmF]^ĦO!ēd[vww~0bDŽ\7cE*HX΂)ܫe+nڴL YbBá f<{Nh)^ Z:|2qG!a=O :!F(Q8YUUJ;Z/ i>ߘLMB!6‘'[ѳOx6E\Vۀ=z-c-1SmXu:yEbAA(x3Sup`3WD9Bk?J:4Cd$BdZTf<]ppH mi 9b҂w_s܌~(ēf4üKQ8Ux޵ݽ16l6=1Nrppt}o2MiӜl W\/]zmwsRHjw#! J 3-5SdapSʩH1i\Ft+z{8T%گV#"V 5+՜}< .?0Im7[eX/VpyX{!ư\\=8\͍&?'nn?i}15Wg>wg'Wsm/pם_p 羇ڥ+;_}KwJ)o_۽ʗhXU˝k]Y)ZX8faLQU-![S H#kgrb)3Xy JѾDP=ak݋Md'B<6F+ޢ?f t ܴFMABϽGt:sugԌG[N-wҕ/]yRJ^ѡzx'_y>GϞ[>zE/Wտ˿7sg]O9#fN!JXT0YɅ1:9%FuE챍)ג88phkQ$r(f07T`IH9Ý 0F*$0 YI fsg7g3~ջ5KAb.^T\-zvg'ٳg67̯o}g>gj?'z>Tz^xj=x;vu'oݔr ?}hvMf4E繘iL%; 7-s&7$"SH]麬ŀ݀)bܡهL\3ք0Թ]  MV$(bd sG"j9uDV$rӄ%˩WUGDp@ ]|JpzvT~tg3'L^kHoܼCs]w\/~{t _׿^m_ju߽w?3-yo7sg>/Fi.}k]|U.;߷}j󥊟䷞xǿ}Xڿ7K-1{]D>XoN{Wv݈<>88\)%MI͌%Rg IM%Dq55 "# #V6UEb"%/&$74 !d&0 V (0׆ V/0~L+zQM`Df]#mV%d=; ۛ3nrљgx՝k?ȩny?lf(?kG TC{K+~xWwX{ם|v^|?W~?o~E<ҕ|ۛ߸wƬ;@)]>yۛ/}=/_[~._y)Wu[%W,w3741xɥ_î[V }hBtb QDѭhN&5s5(U]:jvbtzi7+NL"BL"UyPFi6"]bᦑպnr.(qNѤ)u;׮_W9=KnOl><=>}d{o~?->‡yLxXdwq[зm7_=wYAGz!ғ鷾$7^~wM'x;oiJZ%]rNHXIZD掰Jݪi*`:j7hֆTR_wtt\v9i8:bκw3ۧ6_kkri{}jKyɧmo%A?=Z,~m-fDxK*͍[Tx4ǟxz.^r[nFv/nr3l\%e-DFcR2N^Us* 8pMb+EAMT&]|!i rwA, XߕsjF!BˋZQ/cK]IIMeu5 D~Ģל#ph8#7[,fބF^N9烃݃ݮ_k.jH 0ĦiiΆ1ֲk{x-oo4jyR trg~Iw#=G}SB%GGo|G{"Rzn Ο;/~o>C+uwc^^_}js̖ssn)o.mfzÙ,)'zxΝy'^t'zZ@nxO=swyw>uo<7yʕg}h "џ}쉧yã]wݾ`anc\wຩr@ %iUU@dF!!@Vn0?@vPZ),<@s7¡lf K lf$տ% թ׵ V&`w/nk.NƓ:SJv_.%%#TB$>}s!rf0| |?t ~p]9g[_'?逸ۻj1ۧ.;sSJ)|ox]߃Jsl\>yODܜ_R"7ឝ͍{S dܒ]:}NzaVEBwO}IݑCPNKrȩ Q;mb#@YK*@LTA9EMK,s7kZiF`j}s_h:M+MbҧZf#FK]I#Y1?Nh$@ *@Ջ!TvCjUg|>L&8J|Wի;׮v973sgϜT\t|cҫ?HO|«߫I*b2Ķ˪UÊbbz˩dn9b€f3^ɑ5AĨx_*D &" ;#vdpC+fT(0dIY&!pB섎HjljDjnfY V>+4dF̦^w{{{W\?8tugjGsgϞ;sz6 T--A>}.|m~A֖u}׭%" ` jeЪꂋu89;2!T 9XFY:5 HR;92!gp#3,J=&TĞ"21ƆJBSThb#1xhf  B4f ZPF b?)ã+Wv\֝3lQ,Xtksa+PR]>l"s:W&!# "IB+#f&emZf$tUKCHh$̇ƒm-\Y3`΂A4fV|u (*rvs8PGKSF"DT00wG-Cp [Ҋlro;7V\Ahh:7OdF9x.%k.Z ŵ팈`v WOx:ܜOf'xjKZE6sd+j0,$P$1f Z܋Ucj#,"ALKbB @1p n90O$AG ݚ!W5JܱsC dFŜʺ$ C >6fDȦDTL]{lj̄kDawXXX6d<ؚNFMK}xlOͦu2^mղﻬ^u K.UBh,T7v UT* 6;#"G&b25ϹHCM#斓u]bg!׎E'Q|Zv,cݾEDb L%`` fD.lDPI`nX܁j΀@@Ɓ1iY-#6Vaj7Nn@UsnJ1g>4hp~@5iOPMdssc<X|RJr4v<O'*10s2TZad4]}<*Fcܭ1Hf ͭ!#PJ3X29h.#a qRƻlQ<9͈ jPsvbէfB1)Fm # U 0Q^Ԭ(;&:Q*Rrʮ: {<8;8:e1(BhǍ|ҳq2~<6r֒dtܹ/뺞E^Mm&['"$WP숦,w֚P2F!tzm##H'2U`L,Th`u#U&q _[|ڝ6tEq6jf4o1AVP;PBP{B_\̊ݨKԶa29>RׯSA",fŬEDͪ%MCjЯSJ(GiU3" 0s-RIgPI#Wnj=PDLÄAwbpOHrʓq2~h<np&6AnLʡ.P{€5tMX fŎ|p>kAxu·}qMh0#2r$iĐݡfjP%M @ x5\H M#,~US13{s;sD#a"*v@ZjN[ jqlV=yQrn׋ Blh6W_.)@"rql $8B9 AcDЈɡl,F0˃_"WQ(X*NdX7P>Y=(%@GDHL̎j1a2py ]R21IRP*~)+FcDw͸iCrU0Ct@"@pB9A'l֭!$[6Ef^ N-ɦ 0d6j n5(;hƎFYݨd[p2˾8JҤ`4R$`TOW]B_DY @%w}ݽZSyI܁H &TUuEջ^i31Dz#a f`V7 pX8bC :񪩁  XV0*6D%^ZEpn[O f`uIaАG Hu{"֭ڡn` ê[vuhVJ]F$ F  QX(xm;Sd.jE0-_u0`!2J0Ubf%H^bd"WH tدS^vpbH}Fw"bd"r4dFAL1{<bDH\=֮fANQW9*0Fu#"Pb&, F n{2^UZ,9# ybB,n*ZҥnnMb0D)iLދ "#RvFչlLC23!bV.9ñB weYu{CR,e9-n;6݀$&0t)WKdC)Ud#8@P Pǡj5mRmu).n.BUIղ Z!A@2[kϊw8ga$tG ("~QձYlQơ:{"<f}C<^GܡN!lqs`ī)"lffլ:2d,U۱_Pv;*ղid٫+Si#,&pL͊Sdnf1͊-v1"Zm6^ !x-X9!Űr$@rgh6p8qGE4~cuuWʰ3lf6.nj&jf "Rq ]-4yl ]in5G'ffF4]9fn&D!FS3~nn* 2V_joC\Rڮ|>#vrwT 06+_؉؁ f"@\1v] R$SԴlZLjh_\ޑ!m1)`bG Pƨ1b*:٨c&s[G"BE7jCQCl!\R[,|A`sim/v8Uqfۑ8*Z:jfP-N2gU=Qa0p0 ص DpptD3Df"ĞŘ4ic_׹q ȳq$-㬧 ,j40]^^ބI Tn8wSV1Tk"PmnsMPRPH.JKT 4!]_]'B5B ZŮ!xӲ ZDZ2hi*1%9DDfPq75sjg}8U' 3[4*2sӶmZ.7w_nU"cdRpSuf[CsU]a80f ʫ%}[41aӐbk7nP4dspA DF`!pH%WWsdGtZ)":ܽH8 ,y8L]-kvL?WVka^^\LSQSb]HM]SA nEi9bZ݉ծ*`C,r]RQfX.ex{9Mȡ\j X@|6ٝ0lyȠJ1@pW0t7U"@>&U482,dT};LzVm+&|īE`U_^\\ACLl6bcd3BkfC9_cN"*90#\j~e WRH8Tcu~BH˗~GuZ0 ɯ*#5F%ؕT@"b$rPgk7u=S3"Ϛ~J`"<<O2]gg]j>%Zvw~Wd/]0"<skβkqߛ"3 `6N@ )uMh8ƚx0:ibS܋R],SA&2AԬ slQY4iy,T"#?' { j`-R8_S|kmsl?#a?8 R1.7˓b6ok#6q Od bƈ0w3RbFpQpU(֞S<&vRU_F64oM#O,f- YpS&~M FMPb6NϚS)Ml7&k312u݊Y.FDطM Ul8{G`(^n/..oq\fsrzAdz=o1C7]UGrRv3>/T8KHuin Ԡͫ;Ld H].|s]uq^zU18^_gqD] e1] G.Rηni]ӝ],|/L=;Eq0MXE -|MUuѪ,l`03n/xzـLz{:V#ٵ{5\ :X^v0Tgit62j-5/bű=̈{1w=Tq'nܼu5ZOOV+1ًg5Rm?lm+eswGTjs lR|ùTf̗;qI`U.o>]ߟ^l6i@GMmssD6#q,0; ZSENNVg}݈/ת±Up8ܺqbM߯-+WVMdqi!br0W;*`*ټėE,gYwÔOMjG| ln"罛d9MU*"բF\MT~;4.ۖ˭VRiY];֦t>J`wWa y_Aݱ[O֧]Lf9q L^k&}à&eŗ_w*Sɪ]=h xuAGU|{~K6''w_u/#^l=!"='w s{$2)VW%n.yCp,:5D`DL1[r,+cs]~ NiVTgk˾7/Tnmcu^q?<3jVA#^3nf/ .؜ܵX,F+0>ݬ7ɭ塌mht]x-\V2@Xm墢JU#^Ki0],֧׮/*9\"Ղ#뭛8~*ww!i_r~S8qmC)4vSNq6I԰ܜ^Z߷>{Uun< 5WSS]H!,"s$ $bź漽iվo𻻚rqPhb/ pz63sC@stkb e`: !\VdszUfpx~8pŢڀ@&6vD YE ji1ݡ2n~wjm-7m_ fŨH)#x zo;Du?.EۮrcDts5DsTNyʵӒ(/~&2]l/<1ٽ00M9 YGrW@@v4t;=9YmUdac"sGSP_ dGOk]ۻlCzDH)};lۦMGq7lVͦj;u܎4mst]|;{s@-1MLpib`@tP q߶Sh !xݱxQnmpإUߴ oF}9l.A=\H .."Oe M۶kB۶ bi*7"bF4)DN#&hNZ]aijQLl m xJt5!tm\.ݺZpGnjϹ"D)pDnXB׆b ]9 x$oT@rOfcvERdj\*X)SêL5 IDAT=mcu6L2a|pqp@&sBTBtS+Ub^;tu D EM#^]{` bE Զ"LuHJ9OY No'!8Mܸ7J3c!3sB[ 0cWQp$4q7p8]??<2X5DPpwG$pGtF Dy(%:G7lR{f23}Gq'T0a?iP~ϪY"YZjpC)S^K/~7BDjdSH@n!""0x [۸U*n| xKy] f;6NbY5Z |7]2;HPuED`j: Ԛz #,VkwO"^N vx#8ry>ԉac5B & $ &T^j?, 5O`2JɵIT:09YH/ Ar ų ;qiCѺ- S6tiٷ4\TU**^&IU(^qZc|kD6M`TBb@&+j>ƙDLf)qdspdtF&J}X ѽijm@Ue?q[K&46lFmSkF #I BT5gZ}?ֲjq}k0+K0Bt7tpGwsq`4 UE]%r xl/vy0G30uPC`N6'w^Tfy<:Eܜ,BJ#M0  9SlͻNO6Wus#x _-WMDA4Vb@nfUhXuAm,}IMۜ'e1um4LVssv[Zf k-nWΦFLB(ȹsµ i:uB`" )ClN6wۭ0L233Gp.ήmMAcMݢ_}\*"P5MJh`8]Nu24("Sh8D H`MQ048  J)8oR]Jt,>A`&nTJZ*#9.bw^fٯ܎HU%.7׮1QJi+~7:HRKjp9Cm1!2Q'A ͼ: zuq_yJˮ3*dB b{t>M+H̘صsfq"y{`_f@Q/#| Xa"hJn`aRqM ;0}9{<=~{?{έG#~KIܵ-dS5 bN&V"ln#(QmjYb\ĸD7UCSH:.s=4a@t }CfyS`b̏MJ'|蹇x7̍?iws[_3|e LMLXāȘSw)rwuP׏?М1D cuȗM68]; Pvy*džS#!Pbrgx,~jΜ'f7?zox;7{+=w_7^N77~N!009%d&ȸժMkGQcw#25Q'Saئc۷mڨnZe/Dۧq~~ "sb+`CFhџx'y+ß_K}bkfo{ˏ'ﺫDs?ħ~4_'>ٯ|o={/Zc_vY/>N)>XO?O}G]|7>v=?O}'{/^?/07Cُ8uw'$B4@!&x{*BL E4GnmCpANƶkwg.q`j1r _xK7~\[>u}o|//~tZ~#~幇_⹇ۋ_/6NS/=s_鱯/|mEOz+~~G>/Mzvvt޸~l_#}>ODٿXً~j}thZ\,bsq'4.iOa ETʤzy?Le*!1"A@LT_܎ щpqvƯ~ X.ҟgg~C|?8oy;|9 ή_;}Ks/v>~kO۟c_Ǿ`J]}&,E_?^& @އHa70tB#ZUs.[څ6Qcb{"RbCKjm rV@njՐ̃r-ʜ?O_} =!=nCR9;|O<x/q:j7˫)Kw} 2rK}%w#~4<ܝ{9d,b5\ A C1q]-6U"!Qӭo3BfwMj\UdF5r@wpI֛&&Nmb@U/onwCލڶyqnmK/=/v$!1*"Jq/#FO$D%f{~9IGg`O}TU/.?C?n7#{Ͻoתdηu?矜c}SϽ|_|g'ss /9^݁vw7'u@g+*5ꔫPG+^㥘0St "8RoR,( ;#oV1+ @Bf `H|:Tb FWG(%QKy-o?9*-}s~ɧ7|_h_ɯ[ַbrǾ:3N6nrx|'sϿ?t5("2x<鐧\8:Lh"dHZ/%1#sfE2@#Y)Yıń0@'IË)$n8w6Z$jR,vZbc9oS ?~/߼u^J1ow} |v"Ww?ywm?=^n^-~GߗR.|s׵y[s)?R ~_~{6Z=9YvȣHђ "̈x3UCs>SLkz\76, BzC'X*ZJMu>]+dT1Ojk.ÐkbeR/Z9r3#f}߽oY/O G۟_wH5ݝ{Q@-9FBfժR wp#dq(jkLDUЅ!\ tXZ fET]V#A!˵o5]c"b@Dj@DuDqbkZcHM8TJbNTTb`7+cqeK4ZfgЀL%Oc7bvg`MB rMj AdfY5m۾V8vppsI%E8x50L4X]߹`&c!a0x8ܕ#MLM HUTTte ]Q!,o{7G1yqy:Lb)Qxd'NN, A  ji'`S]ӅT5g%ADLZ0R!JEQ\)rW cΑ1 @MW^{A\eZ+ <ƸDDԤ:tV[Dgȸ$PR9JUzAVOO_]7AL=@@|zgJ12shErX҈(`qi$%H+Ʃ8@YDK1b!űMM DŽdL2p.x `N$V*?%t-eAYrT;ʨjũHLfR*Rh 2FƀNLi.Rd {I qQ # R28XW9)dtNE֦ı1Jd :͉{ {q}u˦`{ᄈRԷw>WT :9GV<ؐԵ[hFXGb:I D 䄀R$ilձ\0$)O.Er{R0 qWB &Cb 2hA/W|$1ND΁@r`ĂR --+2[trzfՎ;ι?r|ySgοWK!>͛64NGNL4]$e՚8 }W3`O>~*rla0FLXoݾׇff/rl&ؑ27_wSg]Շ]nB;hlm{4=s[qFD#*:B3#2g\pLQ[ĈsD` 2F6f$1<ϓO1!8k+j&J'8cdđM'Hg =΂ /s TƹD+/} p|j/8z|H!_T/\29 _ȷm܅K/غn|R,82q]u_|}h9i_=:zV';|뵗gf/:STwز~|58}݂y|a9|Ԇk6mX[СM]h{I6Y~w6BӪnv@ϓ\^rf3VFxDA 3" :.{& r Ĕ3'PU\ M7OIDAT5:iz0n|,R㍁և]nm6rnRq?6>g`#R:G h,g^[D!q*Q~1!L ?Z 8)r!melj2 j UBN^^.u.iH'K2ᎭÃ#}ߜ؈֮9uԶ׭÷_zqe/_3~%<6ӝ{ mCߜ:9~T[& l Q4[e/^޸vmk{IZ&(NkݒulMDqd ::q)I*Kq.98 ,aֺ/"rĝՖ8H3$ W$8:G\"|ѿ_|ydtxpǵjоBYwn?||CZWc7kל>wofv`Ub_}7[H=}:;ǯ޸u۵V';whepGOvn{)xe#?}7ok]?uz|h.=F[-?~G~iiwKRw@|Xpas,'e 38U⨒(Ljt6HIybZTq ֯^*Q+M\IJ\dc޻]@sIV;"8:缬q9 35#0R9w?8~fm}S%]Nuo:!FϞ.Lf/RrlޗiByoHP"h-2@Y!XLơ-42܉Z}L[\[6:l2uB?g {KmKS>)ڵcX`Ce+ݯ:T02bJ\5Nc&#rb%i Atf;8@bȁ1f(jҪN"0+9gӤs`G[ieE){$I:sr\5mti7D/tJ(JSD+k!sPiHzRzcFc.ͭeVj NJMcT`0FLa>߷j`h3RV<۟;XT犥&Fr!k=}! g243Mf;`mYѶ4e퓜U:jjظ0]zNN,Bև(MݿiZDtl_>-*KC[OWF3^9XS>_NbeMcŚqF`sX/d" ǂB0@帢T ag ùL+[* ي>{_t]vc1>4z%~lgmKS~'vm4eMB7v-8DRw]vCJ1<8ѧ_. .ݙ .6GG4V'g:ckLpb!9a!_X " dSه*#j ,r\_7Vznd+X&^bE1B;V-MYW"^/lr0yӠ5mڰ33oá^!00I9"VKJU+C:]J1NcZſeю}2D^Pъ=j H⁙ sA ז=lRm QmKv6Ҫ:+ږ|4:s\HK)j"LsJ[}$MO=9_=: KCFDƕ4J xzH'ZbdBj L\5 ~Ѝn}B"/88rL_rH@ā׌Iz")ȶ"mi'c#/.b%єM ŜHr5GOwK,&݆R֥.(-y1?W2lKZRa@BFrsZelZr|&'O+7sk7nۯXnQʺd'f !c%'$@9$#3px`8~01X "ziy?>˿cϝnLRHdV(U48Hikeq/;X, _1LIJ@חg:*$Fi`lvpp!m=}j{ &.ߟ֗oyՋL @ǴitGsApesBn yN5'N!hzZ}"[32}4Bz2Q#^m,޻<+~p/u}# &9 DBd'y(pd>C8}=hɦ }ރǦ1wna=3&iJ?3qݥ+_\~*k7n=yzȕk7kzUkk?vrV sljzdhpoܾ{?uF֍}ɗIlߟ~|itnGNWwmiúnزyXv֖͛^xz}ԍoRkx6}Ï?oT*Ͽ>/ }7  DuICKD략-4"&;]x}.rB^AƐȄ[`[vn۲1tzc&gl42zdխB,B -P,r`O]m]>-҈T%cvσ[唆\ 5jME:Ò yب叧Ϟ/},#>qG~k7ظ'[?{گ|^#H;8*\;*m˧eL?.]Xձ[Nid;a^AM`gJ!_Qksŷ_eۯ^dW|.[:M_!WKZjGx)"6|ZMv[I ^qOwn?q>|\:pmKC5RW {~%9urW߬ϤVBFѦ kO`?'Ͻmrdh`rbSgaq^Z;R@ghǦv>)hF޸umO|-5Zv1ҽ(eO?n~{gb?e T|:z6Pʞ~ -TU/zzR/HSOzSO=~.Cވ37K=ӳ-tO=z)aIENDB`qbrew-0.4.1/pics/qbrew.svg0000644000175100017510000004563511016417221014405 0ustar daviddavid qbrew-0.4.1/mac/0000755000175100017510000000000011016417370012336 5ustar daviddavidqbrew-0.4.1/mac/Info.plist0000755000175100017510000000170711016417221014311 0ustar daviddavid CFBundleDocumentTypes CFBundleTypeExtensions qbrew CFBundleTypeIconFile document.icns CFBundleTypeName QBrew recipe file CFBundleTypeRole Editor LSIsAppleDefaultForType CFBundleExecutable qbrew CFBundleGetInfoString Copyright David Johnson, 1999-2008 CFBundleIconFile application.icns CFBundlePackageType APPL CFBundleSignature qbrw CFBundleVersion 0.4.1 qbrew-0.4.1/mac/document.icns0000755000175100017510000011207111016417221015032 0ustar daviddavidicns9it32T)}|}9¿[߂ԉ҈Ѓ¿e ߃ԉ҈Ѓ¿q߄ԉ҉у~߅ԉ҈Ѓ¿} Ԉ҈ЃuԉӉ҃xԉӈуtֈԈӉуuֈԈӉуxׇԉӇҁw׈ՉԉӃ¿w׈ՉԉӃx׈ՉԈ҃ÿt׈։ԉ҃wևԇӇсžx؈։Չԃt؈։ՈӃûw؈։Չԃýw؈ֈՉӃ ž tۈو׈։Ԃ wۈو׉։Ձ ú܂ wۈو׈։Ԁ Ƽ߃ tۈو׈֊ Ǿڄ wڇ؇և։ Նw܈ڈ؈։»t܈ڈ؈ֈüw܈ڈ؈ֈĽ|܈ڈ؇ևƿ{ۇهׁ U\{Ճr݈ۈف LKLI\ՁĀd݈ۈـ ׌LNLJG|Հ́\߈މۈ^KONKFQЂ?݈ۈԳFNONLGCzՀԄވ܇׾iIRO KCx׀փ ߉܄ĀNKLKHHCOQPOQLByք׃ ވ܁ wKUlx}|wdFETRQLA~փׄ¿ ވ۽qKXr}zvf>PUSSRSL?ցֽÿވMZv|yvsHAVU(TUDRҷtSKRJ߅>݋Kg~{yrQ;:PXXWVVS>|mNKUfnutMw ߄Kp7~|vE?cKA[[ZYYXH=RDNdrn`JGFVk® Od4~O?pxl9S^][W@:;>@EJFHiƱ ߁#KZhu}|{{|~q9dvy{MA_`X;>RWZVPJCXԀ ʴӘZHCOnvvx{|}PCtvy|s=FbL>\][ZYSDAՁ ϻ 1߸TQfeHCC@<;9?gـك]b|{zwt+uFGgjng9=CIIHFA99glpuy}}zvqmf:XmjeBfنہMxzvtsrd:9Naeimpsvuspl^9_joswyzxtplPimqvyzyvsnZ9amd=Iprqok[8݃ރPc|ywwxR9N^behkmnomlaC9^kpsvsphNPRPLG<9@Rfvj9YjmnoomX8j{yuplg==e_=n݈߀L||zxx>O^@<99>C9J`kpty|R?fjkX9W|~|wrmhD9M>P݉߀8X||ywvc9_imrvy|{BKglpvzy9Zsx||wrma98_asnea^;98TdhmptwyxvFIjnrvxxvsdO:BDotwxwspg;KF^lmh9Vbfjmps#qX;dlpsttspc9aoFIJIF>9;J]sJAdhifEFdU=?ivyyvrm<@g^=c/[bzyvttu>Pfkpg9amrvy{i9;99IpsuuspT<9M;T0Mfvtrkfc>Oglqw@PkpsvwvaRAC9apqrpgA,D`kkllp*_Sjig]D9RJ<9NmtR^S;ikjI@(HOI>:@>8EK9grttrR9]fHA%|s}x:`hipqAKnppg;=E?A$KNhkmmS9flfBOb;n"΋?cicK99Z_?MVEE;MF?9Yݸk}T  ̓ ؀$'$#$#" "" !"!!$!}|}9¿[߂ԉ҈Ѓ¿e ߃ԉ҈Ѓ¿q߄ԉ҉у~߅ԉ҈Ѓ¿} Ԉ҈ЃuԉӉ҃xԉӈуtֈԈӉуuֈԈӉуxׇԉӇҁw׈ՉԉӃ¿w׈ՉԉӃx׈ՉԈ҃ÿt׈։ԉ҃wևԇӇсžx؈։Չԃt؈։ՈӃûw؈։Չԃýw؈ֈՉӃ ž tۈو׈։Ԃ wۈو׉։Ձ ú܂ wۈو׈։Ԁ Ƽ߃ tۈو׈֊ Ǿڄ wڇ؇և։ Նw܈ڈ؈։»t܈ڈ؈ֈüw܈ڈ؈ֈĽ|܈ڈ؇ևƿ{ۇهׁ nrՃr݈ۈف edecrՁĀd݈ۈـ ךdfeb_Հ́\߈މۈsbee`]fЂ?݈ۈԹ_ddcb][ՀԄވ܇ï{_ecbc`Y׀փ ߉܄Ȑgefda_Zcdcbc`Wք׃ ވ܁Éeoy\Zgdccd_Vփׄ¿ ވƒeqxTbfde_Uց¦ÿވeXSs\Tkjjihh[RdZezvc`^k}® i{3`R~{LdmlkfSNPSW[a__zƱ ߁#erMs^TnogORbgkie_YkԀ ʴ2դo_[d~aUPXp^QklkkllkgYUՁ ϻ 1߿mi|y\Qc||Mn}O[V[pnmlkjigdXQփ н0jumQQlaR~wMTRqpomljhfcbXQ؄ ѽot{aLYqOg~qMLfspomjhfda`ZSw؃ Ӿvr_MLLt|~zRNlspnligdba_\Wj؀ل܁mjLc^Ly{|~VNlrpmjgeca_^\XYzل6i~McuaSxyz||}~~~}{ZM`onkifda_]\\ZXwكjXVuxfRvwyyz{|{{nSNORdjjgda_\YUTZـڃ5jrLoxzmLsuvwxyyxof[RL^|`LLQVVRONMRsـك rz*XYvy|vLQU[\ZYTLLPZfv}oaVPOXL`ځ؀6e~}|~sLjwz~vng`^_``Ldv{|x^UvlPyڃۃ6djbZWY]clvcRswz~}U\v{|xZ[yvcPڃڂ5ޒjbvqe[TLLNYsvy}|cRvz|vNh{yvTrنہ5esNM`rux{~~{nLpy}~zaO|}{xjPچڀ5dXRLdpsvy{}}{ysMby||qMm|zvQچ!tuyLkLcnqsvxy{||{yxqUNOuz|}vSZ~zwXvڊ7jfUtN^lnpstvwvrjcZMUvOcxz|}}|yUT{xZu݈9lQavXLLRVXXYRNLLPXmfOswyzztUQ~|wWy݈,0/'ցֶÿވ%0GUVVUTSPOLJ% 221'0"4үW-"*"߅>t")!766443&4 '8EB6$ 5R®  (8RSSTSSTU2T,HOE09872  # "NƱ ߁"2=IQPRSUWI?NPR*!9:3/251*$;Ԁ ʴ2Ѕ7!*EMNOQRT-"LOPRK%<*7876543." Ձ ϻ 1߮1+<=%-GLMNG9LNPRSH($';9876531-"rփ н0(2QPLI68IJ.ILMOPRRB!<<:87531.,#g؄ ѽ113WVTPMKF-%<5IJLMOOPP<2><:8641/-,%P؃ Ӿ=/VYYVSPNLK+@GIJLLMNF8><97531.,*&:؀لL*SZZYWUTRP5/*DFGIIKLKI$8=;9631/,+)%!Oل'NXYXXWVVUG/@- CDFGHIHF'-;97420-+)'%!Jك+;CF9?ABCCDEC:2'*HI,##  Tـك 77NOMLKKL*$&BEHA#(('& '2AORRPI:.$%9ځ؀6"=JIIGFGGH@6CFILB93-*,--0BFIMQTURNJFC,"A8_ڃۃ6")##(/8A/@CFILORUUROLI#*CFIMQUVROKGC''DB/ڃڂ5i/%:80'&?BEHKNPRRPNKH/BFIMPSTQNJFA5FDA"Qنہ5}&LOMKJJ?-=@CFILMNNMKIF:@ACC>60'!@/CFGHHGC!!MSRNJFC'V݈9,>STSRQRR-B& #$&' $9MO2@CDEE?#ITVROKGC$\݈<#MQPOONNA;CFD=50.,*&6@GJMPRL'@AB5"DPTUROKG@z݇g0LMLKJ2, @CFJNRUVRO,"@DHLORUS@/0!CLORRQNJF5ކM3HHGFE/!*@CFIMPRSQN=BEHKNO#LI-DHKORTROK=C%%@#IMMLJGCr݂ބ8X$ 4;62.(-<@CFIJLLKIG-CGJMPQPNKG6@CEGHHGF<"9FILMONLIC/KIF;,HGFC$<ބނ(P1QG!'9;>@BCC9-!"%DFIKLKIG'!LNLIE63DC2ޅ݀7(<9.@DHKORTSI6EILORRP:BJNQRQNJF*0#D+)1@CFILOPOM%(DHKNOONK>,!#ILOLIBs5h$ 7DEB3>ADGIKLLJ5@FIKLLKI>-AFJO -EILNOMAB4 !(HIIF!9_*?-BCB:"/)+FL.91DFE'$(#)'!$)AJLLK/9A'$%e\ig;CDJI!*HIIB % %$.,CEFG0@F@#6M\"|  E@  EB  EC  ED  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  EE  DD  EE  @@  #2Sbjrw{}~~~~}{wrjbS2# "+*4@IOSVWWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWVSOI@4*+" )3>GNTWYZ[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZYWTNG>3)  &.5:>AABCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBAA>:5.& qbrew-0.4.1/mac/qtdeploy.sh0000755000175100017510000001101211016417221014524 0ustar daviddavid#!/bin/sh # Copyright 2007-2008 David Johnson # The author grants unlimited permission to # copy, distribute and modify this script # APPNAME=MyApp ### get system configuration ######################################## # as long as we can find qmake, we don't need QTDIR FWPATH=`qmake -query QT_INSTALL_LIBS` if [ ! -d $FWPATH/QtGui.framework ] ; then echo "ERROR: cannot find the Qt frameworks. Make sure Qt is installed" echo "and qmake is in your environment path." exit fi ### get required user input ######################################### if [ -z $APPNAME ] ; then echo echo "This script prepares a Qt application bundle for deployment. It will" echo "copy over the required Qt frameworks and sets the installation" echo "identifications. Please see the \"Deploying an Application on Qt/Mac\"" echo "page in the Qt documentation for more information." echo echo "This script assumes you have already built the application bundle." echo echo -n "What is the name of the application? " read userinput APPNAME=$userinput fi BUNDLE=$APPNAME.app APPBIN="$BUNDLE/Contents/MacOS/$APPNAME" if [ ! -d $BUNDLE ] ; then echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory" exit fi if [ ! -x $APPBIN ] ; then echo "ERROR: cannot find application in bundle. Did you forget to run make?" exit fi echo "application: $APPNAME" echo "bundle: $BUNDLE" ### query binary for frameworks ##################################### echo -n "Using frameworks" for n in `otool -L $APPBIN | grep Qt` ; do path=`echo $n | grep Qt` if [ $path ] ; then name=`basename $path` FRAMEWORKS="$FRAMEWORKS $path" echo -n " $name" fi done echo ### make install #################################################### echo "Running make install" if [ -e Makefile.Release ] ; then make -f Makefile.Release install elif [ -e Makefile ] ; then make install else echo "ERROR: Makefile not found. This script requires the macx-g++ makespec" fi strip $APPBIN ### copy over frameworks ############################################ mkdir -p $BUNDLE/Contents/Frameworks for path in $FRAMEWORKS ; do name=`basename $path` if [ ! -d "$FWPATH/$name.framework" ] ; then echo "ERROR: cannot find $FWPATH/$name.framework" exit fi echo "Copying $name framework" cp -fR $FWPATH/$name.framework $BUNDLE/Contents/Frameworks # strip libs (-x is max allowable for shared libs) strip -x $BUNDLE/Contents/Frameworks/$name.framework/Versions/4/$name done # remove unwanted parts find $BUNDLE/Contents/Frameworks | egrep "debug|Headers" | xargs rm -rf ### set the identification names for frameworks ##################### echo -n "Setting framework IDs..." echo -n "Setting framework IDs..." for path in $FRAMEWORKS ; do name=`basename $path` echo -n " $name" install_name_tool \ -id @executable_path/../Frameworks/$name.framework/Versions/4/$name \ $BUNDLE/Contents/Frameworks/$name.framework/Versions/4/$name done echo ### change framework paths ########################################## echo -n "Changing framework paths for $APPNAME..." for path in $FRAMEWORKS ; do name=`basename $path` echo -n " $name" install_name_tool \ -change $path \ @executable_path/../Frameworks/$name.framework/Versions/4/$name \ $APPBIN done echo ### change location for bundled frameworks ######################### echo -n "Fixing bundled frameworks..." for fwpath in $FRAMEWORKS ; do fwname=`basename $fwpath` echo -n " $fwname" framework="$BUNDLE/Contents/Frameworks/$fwname.framework/Versions/4/$fwname" # get framework dependencies deps="" for n in `otool -LX $framework | grep Qt` ; do dep=`echo $n | grep Qt` if [ $dep ] ; then deps="$deps $dep" fi done # fix dependency path for path in $deps ; do name=`basename $path` if [ "$name" != "$fwname" ] ; then install_name_tool \ -change $path \ @executable_path/../Frameworks/$name.framework/Versions/4/$name \ $framework fi done done echo ### misc cleanup ############################################### find $BUNDLE/Contents | egrep "CVS" | xargs rm -rf ### create disk image ############################################### echo "Creating disk image" imagedir="/tmp/$APPNAME.$$" mkdir $imagedir cp -R $BUNDLE $imagedir # TODO: copy over additional files, if any hdiutil create -ov -srcfolder $imagedir -format UDBZ -volname "$APPNAME" "$APPNAME.dmg" hdiutil internet-enable -yes "$APPNAME.dmg" rm -rf $imagedir echo "Done"qbrew-0.4.1/mac/PkgInfo0000644000175100017510000000001111016417221013601 0ustar daviddavidAPPLqbrw qbrew-0.4.1/mac/application.icns0000755000175100017510000015434211016417221015526 0ustar daviddavidicnsics#H??????????????????is32664?@=?7 3EaqFSB>GBA =RfsQIF@6* ?btIVf]Ll}\D B`Pj}XhTD|C AeF[`PT_wNX9 HdUpO{lLjH? AQXzVf~ZY@ IjIIr\HGC3 ?UNqRVSP<33DFIgFC2??D6PPN:VWUWO U^{_lZU_YZ Uk|ia^XQ? UjfgbrZP `lfyhke[P U[f`avvV W|ao~tds^ Zzipm[Y X}^swjlufoO aznheaT Yjqo~tqZ daas`_\G WmhkoliX3Q\^a}^^KWX\O 6A )  *I6A&! (;%&PM"9/ FO+%85&)$ &!<=93'8>Ad\lPXOhb]O;6;TstXJqFJFoke]S>7;T{M?RNMqjbZQ@757HwoQ\Zz~|GE`e]UJ>9=hvFphFUXYLFJtWF@=;73'7GzJEfpysCN|Nn}H7>o~NJINUVNGGuKh}X\O77Joz\\w}~wPTsFaFUD39HKRYEiy|JyiRFF}v>43=iyFe^f_Fq[[wYOj>=4?fi^NtGsFAIY:2;>KGUr`[{FKj@39W|kRktzmWAqMyFz\:=m~@DE@TqIjGf_Oi>?jqrEn~IseH@z@<<4<>FTCnsN~yJrP}L<14:^yXXyNWaF{?D:5Nj_AOCZNI`}D55 1;94-'#FLT[=(0.FILMMG 260*"9DKQT@9*,-% "BM, $%0<2+EKGJD<AKV]UK&:$ $74)6DLTYVO":KU\UIIB FMR"9@FKNLC&HPSP&9FM8262 AMV/0F-&W[Q:8:1&DMW_ BMXZ !MX[Q,$!,BJRU4.JSUF$9SUL-H:)I!BOW6 FQN *=JA&INE"@'H%2F-.FN%,4 JF'<3&.&"2I ,;!FQ&.5),l8mk3W I} ('iO V ?\od)W=ich#H??ih32?454-46955!68?9635FFE853 39883=MLM;6<56BU[XA8QRRP<666GisttssGGYWWU:236:9Cmvy|~}\:]]\[T5)4;7Da97Lty}TAFOeb``I569B^`VH8;-6Drv|fF;jjhQ:79>>5:2-387Fe:PRpR?\`]SC53.3;r?C^plhb^YT<4)6g{}zK?YD:>trlga\WR<6"6Z}\<7:7752!5intz~C_wwRH>?@A>_uwTJj83"+6>9g||}?0$*46:<769Kmt}NT{}=q\>w5#2?{;Lipv}Sd}o;{CL|r63#3@u|CY6EFE?6BYwy=v}vWM8hI05 9eVTU>[s|YP{};E8qy53938QW<_pyu=wr=l>V833m~A^krz~C[|BwQQ~834AwMXilssdP?:9r{~~KP659]nvq:hmU;TA::iM5733BVKA9;7;hHG|U738;38pkbH|39C7 8bv~l6uv<494/=ZD77O94NNMKNPRMO<UPWQOM7N\^]PN3 NTTULUdeeTNZNO[nupYQijjiUOUUPa`aponmSLEOTP[tRstsslN1JUTPZvS.OflY^g{ywvaNPRYuwm`SUGO`}_S~~iRPRVWOSKH33RPa}TgjjWsvtj[MKHLSV\eYr[v|yurkQM3EU{qSmWW\u}ztplTMAPdWq\TV}ysnjUN7"UUssSTlY]{vqlhVON3UiVdv b^yrmhb\OR#ISVyfs]YRhvzvoh`UOU"Ur^r`|~o^PU{kWQTPPML7!O[wi`VVYYVvleUH"FNVQU`m|SRkkwR#UIPyl\P\V}VWK#KNiYidkqavS#RcUjh|RTSU$FSyhoZq|rh[TfoTQ$UiV|m]PNNOTaoS|T~U$P|UZo^zaQ}mU%UP{w`RXZ`_VVK$UMNSTQOSdglUtWO#JWTflUnfkWmQ#P~~Y^|fRZV{R[cQ:#LX[rQ\`\WO[qTVuMXM"TmSVhgT|uONN!SzneTVofRbKO U|plnXsriT[RO3SMSjoTwUUVnQUKYy[sYjjQ?NYer|jWRRbgTnSTtzNRSPO\rs_^erWP=R{O\z_[RRXLoPHU~~R}duW_tPNRFSYPV]gRUjtRREU3RKM[vXoYUSUNphVfa[MXPUSuUnUmYSRhMUUOZneXRSQS~``kP3USULQyaMR\OU U|PVLUU3IWr\OOiUL #$%+0.'('&:BC !,* =EGHIIJI0/0/.*2$BFILOQRR) &6442 04("AEHMQUX[6 RK897'6KOSX]^JUZ&'?)/20* DQUX:$SUYW/2:641.(6DC.,MQBQTW[S1>:740,)8GIKLF$/MNQTVYKB?;74/,& .ILPRTT0(JLNPRSSOB?:62-)&'GKPUY]C;#2GIKMN M"<=73/+&"# CFLQV\]4E%/EFGIKKLJ?&261,& !.AEINRV; CH.!5<==9, 6WO)!9?BFILO3FKC' 2DPV[[UOE*#: !!+6<@EKRY`a[UGBHOV]]VNH)*F4# 8<3)?DIOTXXTOL8GNUZZTNHAIF #9HMPS('=AFJNQQOKH")FLQUUQL-!RLG1##GLQWJ&&:=BFHKKIG7DHMPPN=MUOHA$CINT\5&,/6;A@80&=%,FHKLBC\XQJE$&CFLQY6>(#,9PWH>EF68Z`ZRL=$6?DGKD@FMU\d^?BGNTZ\,5!7QW[XQK+$<69?F3!@FKRX[YR;GNU\^T! HTVTOJ ##>CHNRTRP&*GMTZZVOH?0MRPLE#GLRQ%;@EHKNLK*FKQUUQM+$OJ)LLI*"8HNV8;<@EE6%6ILPPN=NSLH"H@#DHNX. -EUEDHJKDBZUOI1>",BFMFB;;:A;BHOU]&&FG<6[_XPJ2!4>DI+%CIPW_]9HNV^X-%8SZ_XPK 6,*,0CHOU\\-&GMTYYH?QVYVPF(,2@FKPSTCFJPSSQA9LSTRM*>IQ4BC6'AGII$&aZN+*9ML%,"7SOQ! H'B~ ڑ=yCMB + | LK F/[:J*#g}K(bF۸/> risuait32j16776534976-69773 $699:;::9976* 079;==>=;9966 69:??@@A?;9971379>BBC@:976399CDEFFE>996(79?FGC:964399FHHIG=97569AJKJA97675685639:JMNMD995*478979DOOPOONF99524799:AJRYXVTPF=99:ORQQPH972479FZkmlkkbA9FTSSRRF977 3799?Shoppq pponna9;TVVWWVUUTSC97" .699?Zpqrsttu ttsrqpF9JXYXWWVUS?96+. 679PUH9766<69Cknpruwz|;9jw99764'D-79@LUiruwz}T9UQ9MoonnmlgB9FZqz|~|9=:9_qqponF9Aafedba_^][ZXWUK=996+356799;Tv~-G9an9;hsrqg9;fihgfeda`^]\ZXWURF9963E)599:>99>]}m9D\9@ptsU9Kmlkjhgfdb`_]\ZXWUSK;963G579>YlodF99AeJ9iM9FtuQ9Vonmljihfeba_]\ZXVUSN<96.I699MmrrsstaB99Fo{9=C9HtP9Xqponlkjhfeba_][ZXVTRN>971L69>astuvwwyyw]>99U|[9U@9F^9Hsrqpnmkjhgeb`_][YWUSQO?973 *69?kuwyz{|}||yW:9@f}~~@9p B9D::ntsrpomljhfeb`^\ZXWUSQN@975-89@mwz{|~qF99Ls{{h9D E9:9Ivutrqomkjhfda_^\ZXVTRPNC995 79Boy{}~`<99964"69Qvz|G9Cjoqh9Bwy{|}~'|J9nopqrrstuuvvwulZG;9VzR<9<@@?=;975-579Likmprtvy{}B9_suy|B9JQX_gopoommkidZRH@9;MfjQA:9>;970"79]gilnprtvyz|~l9@ptvz}V9#:CTi}}wneaH9UnpjB97/99eruy}}zurh9:w{yvtqoH96 577568Keh`XOF<9J;EO?9anpsvz|~{R9Qquy|}yurS9N}{yusph:73)79Uuwy{|}~ym`QE9;imoruy{~3}zi9?ptw||yuqA9l}{wtroM96 69Lvy{}~0h;99Dilnqsvz|~}{wuA9asw{~~{wt_9A|zvspe97$ 79Dsz|~{=9A9Mhjmortwz|~}{yvsT9Lsvz}}zvo>9h~{wtqnG94 69>oy|~I9JH9Vgiknpruwz|~4}{yvtqg9;ouy|~|yuM9N}zuroV97$79by{~_9?iF9Yggilnprtvy{|~~}|zwusqog:9Xsvz}}zwa9=~{vsp`9669Ivz|:9`oE9[g jlnprtuwyz{|}5||{zyvusqod>99?puy{~~{yn>9l|wtpk:727:lw{}e9HnqD9\g ikmoqrtuvwywwvusrpmY<9IF9Xsvy|~~|zpA9P|yuqn@9369Stw{~@9_psJ9Vg iklnoqrsttuttsrqpn[B99Qn9>ptvy{}~}|zrB9I}zurn?9359@otw{}s9CortT9Lbghiklnoppq3phZNA99EkG9Wrtvy{|}}~~}|{ysD9F~zurn=9*69Wqsvz}L9Ypsv^9>FNW]_adgikljf`[VQI>9Ciq9>nrtvwyz{zzwrF9C~zvrn:9'7:ipruy{~::lqtwvVH<9::9?N`tI9Tprsuvwvg@9@~zvrl97%69Hlnqtvz|~r9Foruy||n^OGEB>;9:9fhkmprtvy{}F9_oruz}D9`oruz}G9KlmnomY=9:f}yuqH94G69Lggikmoqsuwz{|~~z9;koruy}W9Sorvz~~<9XkdQ=9K||ytq=9369Ug>ikmoprtuwyz{f9Ekoruy|l9Forvz~j9;?9B9A=9M~~{wsf9779[g[hjlmoprstuv^9Iknqtw{~::lrvz~S99@PfM9Xj=9O}zvsP97!79`g_YSMMQUY]afkqrW9Kjmpsvz}9nyj<9Q|yup=9779H>9):@?9Nilortw{~~i9Aquy}{wt\9B}|zh<9Q }zwt\9769 ?NWYVSOKGC=9 Qgjmpsuy{~;~|yC9_ty|~{vq?9d~|yg;9X~{yurF95"6679@rwz}{?9Tgiknqsvy{}~|yv\9Fsw{~}zuU9F}{wa99` ~|zvti97 (79dvz|_99Vggjloqsuwz|}~~}|{yvtg:9gvz}#~{wn;9q}zv[99g}|zwtrL9569Ktw{~y9Qg jloqsuvyz{|:{zywurY;99Mtw{~}zvK9P~{wtT9;k}}|{zwus`97379guy|A9F9Lg hjlnprstvvwvulS>99O;:ivy|~"~{wY9>}yurM9=o{zywuso@9559Fsvz}p9DZ9Egikmopqrstn_I92ElZ9Ftwz|~~|yf99v~zvsnC9EvwvtsqQ9779^svz}E9\k9:eg hjkmnophZMA9:Qs@9]uwz|~!}|zn=9g{wsp\99\tsrqe97."19@nruz}t9AnrF9KRTWZ\YUPLGC=9puwz{}~ }|{ysD9U|ytpd99=mqpjB9569Toruy|G9YqtW9;>JYjw}L9Psuwyz{{|{{zyqF9G}ytqh979RojE97:69`nqtw{}9>mrut^ZWTPLHMU^goF9\oruz}99gstvwwywvlA9E}yuqb979?hF971:37;imoruy|~d9Lpsv{~a9Oorvz~^9Blrstug=9J}yuqZ9679<963\59Ehknqsvy|~L9[qtw{z9Borvz~H9Fmqqrrsr^:9P}yuqQ9569960Z69Ngiloqtvy{}~;9kqty|=9jrvz~B9KnoppjM99W}yuqG95-76W69Yggjloqsuwz|i9Dnqty|Q9Vruz}|<9QlgR;9:^}ytm:7(:79^gghjlnprtvwS9Qmqtw{p9@puy}p:9D9Cy~|wtY9769`gOikmoonmI9Wmpsv{~B9`ty|b99:KE99Q}{wsF94 79bf^SH?<;989[loruy|5`9Gsw{~b]re9<<9`~zvl97369MA9<:=?:9_knqtw{~~;9iuz}}zuD9do9:o |yuT9669<[krw~;9diloruy{~S9Ptw{~!~{wW9C\9@{~{wo<9269679]ty|9;ghjmpsuy{}3}o9:kvy|~|ze9:|K9O|zvR96.-59Crvz~9>gghknpsuwz|}~~}|zwN9Htwz|~}zo=9l}=9e }{wm:7. 79^tw|9:g iknprtvyz{|{zwvtm;9_uwz|~~|zuE9Xg9=v}{yuM96-99bgikmoqstuvw%vutp\F99?puwz{|}~~}}{zvO9GN9I}~}{yvi97/59Mquz}J9Zghjlnoqrsq`L;99AE9Ssuvyz{zytL9>y>9\|}|{yvqB9679_quy}a9Og iklmnldXMC:9 Oq9:hrtuvwvoE9@}~f9;pz{zywvsJ9739=lqty|w99=AFINLIFDA=9%Ge]9Alqsttuutj@9E~zN9QwvutU96-69Ilpsw{F9BN\l}J9Gnpqr`<9K}yh9;putts_973O59Pkoruz}L8S[YVZ`ioq;:kty}A9NnnoiJ99R|wU99Urrqf996N69Vjmpsw{}G9^mquz~T9Rtw||;9UbN;999fnrv{L9Zuz}"\9=Sf=99:r}z`97)69=9769:g!iloqsuwz{{|@9bnrv{u9Bsw|owwP9MS9K} }{p=965996279fg a\XTOIEB?<99]nrv{C9]vz} |z]9;~~;9h|~~}{yI96176379b\M@989Wmquz~s9>owz}}zi:9r[9Cy{|}|zyZ98(79C9CJRZeov~^9Qmqty}N9Ouw{|~~|zq?9b~@9_yzyw\9716997 9emquz~h9Ilosw{;9fuwz{|}|{zsE9R^9Iuvwwvv\975%276'79blpty|z9>jnruz}^9CruvyzwoC9Ft9:ptY976 69Zkosv{~A9^lpsw{}C9Trtuvi>9I}zD9ZqrrV975:49Tjmqty|W9Oknqtw{}n:9\qrssts`;9P|t<9PogF97559Nhloruy|~m9Ailortwz|}~ {L9;aoppnQ99W }zN99FO;984249Hgjmpsvy{}|=9_ilortvwyz{hC989=fiV=99^}{b9795(/9@ggjmpsuwz{R9IgjloqrtuaE99HR9=:998;t|~~|{p=9783'79]ghkmprtuvo::_gilnmZA99@dG99NlL9Qz{|{ztF98/+'69Lgghjmnprm]?9Dg_SG;99Bbjr:;rwywrF97(19;dg\PC99899<9>9U~|F9]uuvuqF97,69DF@<9#;J\f99BUlw9;u}r<9HrssoE97$297#8NpsvmqP9S|~yG99:npbA973*441/,19=mqtw{}t9:q|~}{T9769aN998 79\ortwz|}~P9Oz{|}}|z`97279:98569Flortvyzz{zm99qwyzzy\9966975.379]moqstuvt[>99\tuvvV99636379Ailnpqq^A9969IqrrP97579RikiWB9974-9>ndD97469;KE:99749:H99726975479975337416763JPOKNRPNHORPOI]rRUSRu_RYy|{zywvusrqpnmdVRRPEFJNOPRRSm^RyRT~}RT|~}|{zyvutrqpnmj^RRPDEAORQRVRRVuR\tRXmRd~}|zywutsqpnmkbTRPGMPRWq|^RRZ|/bRfR_iRn}|{ywvtsqpnlkfURPIPRRf@y[RR_RV[R`hRp~|{zwutrqonljgVRPI UORVz?tWRRmrRmXR^tR`~|{ywutrpomkjgWRPL UNRXoSRX}YR ZR\SS~|{yvusrpnlkifXRPN KPRY_RRfR\ \RSRa~|zyvtsqomljhf[RRP QRZvTRTslRrkRv}{zwutrpnlkige]PRPE ORXWRRX[R~$~SRS~}{yvusqomkjhfd^SRPHLRT\RSVR %\RW~|zwutrpnljhgeb_URPO$IPRu^R[tRS#bRY~}{yvtrqomkigeb`^VRRPNDPRi`R[RZ(bRU}}|zwusqomljhfda_]XSRRPO<,PR^tR[yRd#hRRo~|zwvtrpnljhfda_]\YURO,KPSRS~sRj&kRR]}{yvtrpnljigeb`^\ZXURRP.ORh`RlvRh(YRSRSi}{yvtrqomkigeb`^\YURRP3.FPS|RZzRf#dRRboRRSh{~|zwusqomkigeb_ZURRPPQRhTR{Ra zgRRTwjR[iswwusqomje_[WSRRPOJJPT~{R`RWraSRnjURTYYWUTRONK5NRgZRw[Rdjpw~{sjaYRTf|jZSRVTROKPRu RYmR#S\k|z`RnZRP*GRU CdRn}rh]TSUWYZ\]bZQfbRuYROMR`TRTuRe]R}TRLNR`la][ `hoyFvRdRZYRqRPBORNU[gq~hRoVR}RS`RPP NPRd}ypg^VRT\hWRy3kRjlRgSPDAPRmvj\RS2RWYRfRP .PReTRR\3YRzvRY}RP6 PR\URYRg3lRfWR~`ROAMRVaRdaRoRTfRgnRP}R]R]RTXRZRYVRf~ROORtKuRbSSlRRXi}fRpURh iRPRPRywqlggjnquz~pRedRp{VRURj VRPPRaWRBSYWRhRYtRZTRj tRPPR Xgpqnkhd_[UR%j[RwXRz~TRp _RN9NPORXXRmtR_nR^yRRw RP:8CPR{vRRoSRTRsRR~ fRPOReRk :qTRRfeRimRSzRPGOR~YR^RelWRRhTS"qRWgRUXRNMR^R[sR^waR ]rR_!|RR[R]jROORv\RtRS} rgZRSjXRuVR}tRRt|RPK"LRYRZ^Rekmprurnje`[URUduRW \Rm|RRUZRONRm`RroRWdqeRi^R_RPRk]RP9PRyRWvrolie`emu}^RtRR~ZR]{RPRW^RPF:DPT|ReyRhuRZ~VRbrROPRURPI;MR]eRtR[`R_vSRijROORRPD9ORhTSVRZRefRRp`RMKPOWORrR]iRoURjkTRSuSPCORw4lRjRYRR\R[rROPRzOaRpZRwzRRSe\RRj^RN OR{~vlaWUSRQRt5wR`zu|RTURwRP3PRgYRSUXRRw4TR\RzRSmROORTsTR}kRi!oR[sRYURMMRPORuRTRS }RRdRhjRPKOR\RV3gRaVRUR|SPHQORvRS~TRw]Ro}RUfROGRUVR|t^RRXhR_gRbRPGORfbRtzfTRRY]RkeRVWRtZRPPRwyRh;{pf\SRRShRS]RX}RSbRPFRVRSVZ^bhfb_\ZVSR `{tRZXR]gRjmRPHNRb_RQ[htbR_wTRd~RTwRPLHORidQltqnryRQYRgbRRkmRRm~RRO&ORo_RvlRk$TRnzgTRRUTRRZyRRO,ORv[R{RVR[QRnfRNPRnRRP&PR}VR~dRrtRVk}VRRTyRPAORVRPMRS!YR{R[hRekRe URNNRRPMPR~ {tqmha]ZWURRt[RutRTSR bRPJPN3PR{tgYRQRoRVSRsR\rROCOR[R[dks|tRigRhVRyXRwtSPM&NRRPNPR}~RdSS}^RjuRasSPM9KPODPR{RWuR\[R^RSqRPL!ORtYRw[RmWRa\RspRPL:PRmoRhSStyTRiURh_RPM(NRhRZdRSziRRo gRR^hSROLKRaURw~[RV}nVRRuzRPRN/(JRXjRdy^RR`kRVSRRQTURPOI'PRvSRyqZRRY{`RSfeRj_ROJF0PRfuWR]wl`TRR[{ST^RPCHRT|ui[SRURVQm]Ru]RPBOR\^YURTbs~RRZmRTTRa\RP<KR&POPQhhRl_QRR{YRO?LNMJBIRVRS lROORzhRRP8PRtiRhwRPMPRPONR`RR tRRMPRPNK?PRusVRRtnRRNINDNRYuYRRPRaiRPM3PRjp[RRPOKRW|\RPLIRTe]SRRON.RSaRRPKORPNO$PRRPO3?NNL/OONG      ! "#""$%$& "*.-,*' &'('&  .;> ==<<6))*)((' )9?@@A@@??>5*+**)) .?ABBCCDCCBBA@ ",+*) +?BCDEEFEDDC5-.--,+(  '=BCDEFFGHHI HHGFFED'0/..-,,# 7BCDFFGHIJKKL KKJIHGF-2 1100//.-,+9HXD =BDEFGHIKLMMNONMLLJD&,43 22100/.,+=K[aV &@BDEFGIJLMNOPQR QQPOM3"65 443210/.$ 3GZ\SG;/%-@BCEFGIJLNOPRSTTU TTSQ*)LN,65543210.&9MWR>(.?ACDFGHJLNOQRTUVWX WVU1ASTU778776 5431,"/MRK7&'>@ACEFHIKMOPRTUWXZ[ ZX:@STUVU):9887665( ',!6<>@BDEFHJLMOQSUVXZ\]^^][P9RSUVWXD8<;;::985#% D%+:BDEFHJLNOQSUWXZ\^__^\)+QSTUVXYZ'%>==<;:7'33210/.-,' .@FHJKMOQRTVWYZ\*FJRSTVWXZ[O1?>>=< 466543210.-,,*#*DIKLNPQSTVWXY-W 4PRSTVWYZ[]<8@@?668876654210/-,,*(1HKMNPQRTUUV.;NPRSTVWXZ[\^0=A@*#;::976654310/-,,*)# .=?66KLNOPQRS/"8OPQSTUWXYZ\][% AB'+=<;:987664320/-,+*(% &>ABBCC5 >KLMNOP1FKNPQRTUVWXZZ[\U A&,?>=<;:97664310/-,+)(%6CCDEEF D1*GKLLM.+MNOPRSTUVWXYZ L 0!A@?>=;:97664310.-,+)'& =<:9765421/.,+*('% >FFGHIJJKJJ@ %BGG8JKMNOPQRSTUVVW!VM!CBA@>=<:9765420/-,+)'&% ?FGHJKLMMNMMLJ3/D*/IKLMNOPQRSSTUTO(2DBA@>=;:8765310.,,*('&$ ?FGIJLMNOPQPPOML@7IJKLMNOPQQRSTTS"RR89DCA@>=;9866421/-,+)'&%# :FGIKLNOQRSTSRQPH?HIJKLMNNOPPQQR$QQPPF?DBA?><:9765310.,+*(&%$" ,1FGIKLNPQSTVWWXWWVUSM8=0FGHIJKKLMNNOOP%OONNL"<:976431/-,+)'&%#! -BEFHJLNPRTVXZ[]^]\ZX0;@BC4"DEFFGHIJJKKLLML"KKJIG&,A@?=;9865320.,+*(&%#! -<:866420.-,*(&%$" ,&ACEFHJLNPRTVXY[\]][X )ACDFG2'BCDEEFFGGHHIJI%HHGFF9':<:976420/-,*('&$"  ;ACEFHJLMOQSUVXYZY6@CEFGI5%ABCCDEFGHG#FF=##L,&6976431/-,*)'&$" &>@BDFGIJLNPQSTUVWP6BDFGIK9!@@ABCCDEEF C5&3PUUH(&/331/-,+($ 8=?ACEFGIKLNOPQRST6!ACEFHJLG>?@AABCCDEFD;/! +MUVWWVTF(6&:<>@ACEFGIJKMNOPPQQP3BDFHJLNP$(-38>A@>==;:6/(!%6ISUWXYYWVTQL;'"19;<>@ACEFFHIJKLMMNN<@CEFHKMOQ+"*9HMORTWYZ[ZYWURPMKHE>65 ,>@:Z899;<>@ABDEFFGHIJJKK#,ACEGIKMPRRF?6.&!%8@DFHKMPSUXZ\]\ZXUSPNKHFDB?"2CA@: 9;<=?@ACDEFGE@@BEFHJLNPRTVWXYYXWUSQOMKIG)(ADFHKMPRUWY[\[YWUSPMKHFDA*&JHGFDB@:+DEFGHIIJF<3(:=@BDFGIKMOQRTUV3UTSRPNLJHF9@CFGJLOQTVXYZYXVTROMJHFDA8POMKIGFCA> &>FGIKMNPQRSTTSQ!#!,9:<>@BDEFHIJLLMNMMLKJHGFECA8?DFGJLNOQRSRQPNLJHFD%%SRQOMKHFDB?,%5FGIKMOQRTUVWWV2: .99;=>@BCEFGHIJKJIIGFFDCA?9-CEFHJLNOPQPONLKIFE5LUTRPNLIGEB@4"EFHJLNPRTVXYZZQ4?09 ;=>@ACDEFFGHGFFEDBA?6@DFGIKLMNONNLKIGF>;WWUSQOMJGFC@< ?ABCDEFEDDCA@>.",CEFGIJKLMLLJIGF@&YYXWURPMKHFDA>*CEGIKMORTVXZ]_]4@B#,9 :<=>@AABCCDCCBA@>0'N=?CEFGHJJKJJIGFA!T[[ZXVSQNKIFDA>?CEGIKMOQTVXZ\]@?AD*$69:;<=>?@@A@@9/&:QRR ,BCEFGHHIHHGFAR[]]\YWTQNLIFDA? ,ACEFHJLNPRTVXYZ#.@BE2 &,24579;<=<740,("9QST@>BCEFGFANY\^_]ZWTQOLIFDB? ;@BDFGIKMOQRTUVSHJMOQSUWX VC6@ABCCDB, GSVY[]^\ZWTQOLIFDB48.<=?ACEFHIKLNOPP.)?BDFHKMPRUWY\]^\ZWRPG;?ADFHKMPRUWYZ&XV/@ACEFGHJKLMM 3?BDFHKMPRUWZ\_`]ZXUSP4?BDFIKNPSVXZ\\[YWS #=>??=-6JMORTWYZZYWUSPNKHFDA!G%99:<>@ACDFFGHIJF?@ABCDD2"<>ACFGILNPRTVWXXWVUSQNI@BEFHJMOPRTU,TSQOMK$-BDFIKNPSUXZ\\[YWUROJ5?FC@=F:'LOQR QPNLJHFD?"Q&:=?ACFGIKMNPQRSSRQPOMKI8@DFHKMPRTWXZZYXVTQOLJGFC0IGF9(LOPPQPONLKIFEC0 &,-,)&# (9<>@BDFGIKLNOOP;ONMKIHF3CFGJLOQSUWXXWVURPNLIGE@5JIGF7,MNNONMLKIGFDB !AEFHJLNOPQRNF+9:<>ACDFGIJKLM:LKJIGFE0 CEGIKNPRTUVVUTSQOMKHFD,LLKIGF53LMMLLKIHFEC:6EFHJLNPRSTUU3,99;=?ACDFFHIIJ9IHGFEC98DFHJLNPRSTTSSQPNLJGF>@ONLJHFE/7KKJJIHFEDB%$CFGILNPRTVWXE)9H;=?ABDEFFGGHHGGFFEDA.%CFGIKMOPQRRQQPNLJHFE$'RQONLIGFC+@ABDDEF9EED=*&9EFHJKMNOPPOONLKIGF-KTSQOMKHFDA&?GFFEDB? BDFHKMPRTWY[=/9;<>?@ABBC?3!2;S. CEFHJKLMMNMMLKIGF7CVUTRPNLIFEB>EEDCBA(2BEFHKMPRUWZZ1<89 :;<=>?@9.%5'ARTUQ1DFFHIJKKLKKJIGF>6XXWVTQOLJGEC@00CCB@6 ">BDFHKMORTVYA?B$)+-/1/+(% #1GOQTVWX>?DFFGHIHGFC*XZZYWURPMJHFC@6=A@<+?ADFHJLNQSUW .AC,#-:FHKNPSUWYZY$'BDEFFGFFA  UZ[\ZXVSQNKHFCA9)?<94>ACEGIKMOQSH>BDC2.,*&$ $+16=1?BDFIKNQSVX[\\M8BCDEFE<MX[]^\YWTQNKHFDA69::=@BDFGIKMOQ6$@BEGILNPSUWXYYXW4&?BDFILNQTWY\^^\1=ABCD7"NVY\^_]ZWTQNKIFDA.::<>@CEFHIKMN$0@CFGJLORTVY[\\ZXF?BEFILOQTWZ\__\V! ?@AB2'NSVY\^_]ZWTQNKIFDA( Z&9;=?ACEFGIJH;ACFHJMORUWZ\_^\ZU;BDFILNQTWY\^^\YO$>?@@;%,LPSVX[]]\YVTQNKHFDA V.99;=?ACDFFG9>ACFHJMORUWZ\_^\ZW',BDFIKNQSVXZ\\[XVG(=9)2JMPRUWZ[[ZXVSPNKHFC>394;=?@BCEF)'>@CFGJLORTVY[\\[XV?@DFHKMPRUWYZZYWUS>FILOQTVXYZYWURPMJHFC.59O;<>??>=!,=@BEGILNPSUWXYYXWUQ4CFHJMOQTUWXXWVTRO5$(IKNPSUWXXWVTQOLJGEC 683*! 0=?BDFHJMOQSTV5UTSQ3 CEGILNPRTUVVUTRPNL41A63KMORSUVVUTRPNKIFE<&3<>ACEGIKMOQRS4RPOH:DFHJMOPRSTTSRQOMKHFD5=>LNPRSTTSRQOMJHFD*/;BFIKMN7;=?BDFGIKMNOPONLK)'CFGIKMOPQPOMKIGF,TT0GMOPQR QOMKIGF?1CFGJLNPM99<>@BDFGIJKLMLKJH>;DFHJKMNONMKJHF7FXUR#&KMNOP ONLJHFD(.AEFILNPRL99:<>@BDFFHIJJKKJJIGFF&!CEFHJKLMLKJHF>;\YVSH6KMN MLJIGF= 2CFHJMPRTU9:<>@BCEFFGGHHGGFFEC=3DFFHIJKJIHFD,_\YVSP7DKLKJIGFD% @DFHKNQSVY69:<>?ABCDEEFFEEDC@0@DFFGHIHGFE& Z_\YVSPM%"HJIHGFE9%ADFIKNQTWZ"09:<=>@AABCBA4%)BDEFFGFFC$O\][YVSPMF0HGFE@3ADFHKNQTVY3'90;<=>?=6-%&?YN8BCDEEFFEE?GY[[ZWUROLJ7?FEB# =ACFHKMPSUXD#&%" / 6NX[^`0=ABCCDDC:JUWYYXVTQOLIF&(FFEEDC+ "=@CEGJLOQSUS &0@A3#KRTVWWVTRPNKHF9@DCCB3O'??:"(KNPRTUUTSQOMJGF*+BBA7N,;>@CEFIKMNPQQ 3>ADFILOQ*)CFHKMPSUWXYXWF,6&FKMOQRSSRQOMKIFA@@3,2:ADGJMPSG@EGJLOQSUVWVUS;,IKMOPQQPONLJHF$:,&79;=?ACEFGIJJK8>BEGJMPSW$/DFIKMPQST"SQO0)8AJLMNOONMLJHF39!;=?ACDFFGGH6?BEGJMQTWBCFGJLNPQR!QONL?FF&%)$HJKLLMLKJIGA89C61-*&"1>BEGJMPSVW1EFHJLNOOPOOMLJHF1HH8HIJJKJIHGF! 61&,>ADFILORUW@?EFHJKMLKJHF9?SP0FGHGFF/#)/6>DHN1'=@CFHKNQSUW&&DFFHIJKJIHFA5TROI3F1&8>ADFILOR8#=@BEGJMOQSTJ7DFFGHIHGFA(TSPNK2"DED0$ 6=@CFHKNPF<>ADFHKMOPQR2BDEF >QSQOMJC?C. 0ACFHJLN,&<>ACFGHJKL=0ABBC4'MPPONLJGC'?9 &:=?BDFHJK<:=?ACEFGHI G$5?@@=',LN MLJHF& &"9;=@BDFGHI4;=?ACEFG8 8:,2JKLKJHG5!99<>@BDEFG)#9;=?ABCC4 H(DHIJ IHG@'29:<>@ACDE>49;=>=-6RVT &;%(FGHGFC &%99:;=?@A=184* 5KPRTTN9JOLABFB 69 :1'+ILNPQRRQOMJG1DA"07+;KDCJLNOPONMKIA B> &@BEACFGHJKLLC@HIJKKJIG*5&0?ACEFHI'&FGHF3 =?ACEFGF=AF 0 2=?ABCDED00CDED,;=>@AB1!ABA'(;<:,?6 $! t8mk@>ҡJa~-:l. lO?7[q3ePF bi=LW3@,e%W#Wf,-wQU P'w Ss\vϢ9彖nF ^X MAei&4 F n1Z-Nz"(:0h/ \Wxrϯ jl 8-#V v5Mz3h۽ zh_,C $-H%V"ݖX Bv"m1' } (^<gy^|L)qH`=]ZV{IX0o-0.=SaH04B}qy,-xiM"SBb =W oC~qbrew-0.4.1/data/0000755000175100017510000000000011016417345012511 5ustar daviddavidqbrew-0.4.1/data/qbrewdata0000644000175100017510000006257311016417221014414 0ustar daviddavid Generic malt American two-row American six-row Belgian two-row British two-row Canadian two-row German pilsener Belgian pilsener Lager malt American wheat Belgian wheat German wheat German dark wheat Rye malt American vienna German vienna British mild Peated malt British brown Belgian biscuit Canadian honey malt American Munich German Munich CaraPils CaraVienne CaraMunich Belgian Special "B" Crystal 10L Crystal 20L Crystal 30L Crystal 40L Crystal 60L Crystal 80L Crystal 90L Crystal 120L British carastan British crystal 50-60L British crystal 70-80L British crystal 95-115L British crystal 135-165L American chocolate malt British chocolate malt German carafa American black patent British black patent Roasted barley German roasted wheat German carafa chocolate malt Carafa I malt Carafa II malt Carafa III malt Maris Otter Malt Belgian aromatic malt Golden Promise Malt Rauch malt Light malt extract Amber malt extract Dark malt extract Wheat malt extract Light D.M.E. Amber D.M.E. Dark D.M.E. Flaked corn Flaked oats Flaked rice Flaked rye Flaked wheat Torrified wheat Cane sugar Honey Belgian candi sugar, clear Belgian candi sugar, amber Belgian candi sugar, dark Brown sugar, light Brown sugar, dark Generic Hops Ahtanum Bramling Cross Brewers Gold Bullion Cascade Centennial Challenger Chinook Cluster Columbus Crystal Eroica Fuggles First Gold Galena Glacier Hallertauer Hersbrucker Horizon Kent Golding Liberty Magnum Mt. Hood Newport Northern Brewer Nugget Perle Pride of Ringwood Progress Santiam Saaz Spalt Sterling Strisselspalt Styrian Golding Target Tettnanger Tradition Ultra Vanguard Warrior Willamette Zeus Generic ingredient Finings Irish Moss Polyclar Silica Gel Apricot Extract Cherry Extract Peach Extract Raspberry Extract Orange Peel, Bitter Orange Peel, Sweet Star Anise Corriander Seed Cardamom Seed Brettanomyces Bruxellensis Brettanomyces Claussenii Brettanomyces Lambicus Lactobacillus Delbrueckii Pediococcus cerevisiae Ale yeast Lager yeast American Ale yeast American Lager yeast American Wheat yeast Australian Ale yeast Bavarian Lager yeast Bavarian Wheat yeast Belgian Abbey yeast Belgian Ale yeast Belgian Fruit yeast Belgian Lambic blend Belgian Strong Ale yeast Belgian Wheat yeast Belgian Witbier yeast Bohemian Lager yeast British Ale yeast California Lager yeast California Ale yeast Czech Pils yeast Danish Lager yeast Dry English Ale yeast English Ale yeast European Ale yeast European Lager yeast German Ale yeast German Wheat yeast Irish Ale yeast Kolsch yeast London Ale yeast London ESB Ale yeast Munich Lager yeast North American Lager yeast Northwest Ale yeast Pilsen Lager yeast Ringwood Ale yeast Scottish Ale yeast Trappist Ale yeast Trappist High Gravity Weihenstephan Weizen yeast Whitbread Ale yeast qbrew-0.4.1/qbrew.pro0000755000175100017510000000630411016417221013441 0ustar daviddavid # Copyright 2007-2008 David Johnson # This project file is free software; the author gives unlimited # permission to copy, distribute and modify it. TARGET = qbrew TEMPLATE = app CONFIG += qt warn_on MOC_DIR = build OBJECTS_DIR = build RCC_DIR = build UI_DIR = build INCLUDEPATH += src VPATH = src win32 { RC_FILE = win\icon.rc CONFIG -= console target.path = release trans.path = translations trans.files = translations/*.qm data.path = release data.files = data/* pics/splash.png win/qbrew.ico README LICENSE doc.path = release/doc doc.files = docs/book/*.html docs/primer/*.html INSTALLS += target trans data doc } macx { CONFIG += x86 ppc QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.4 QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.4u.sdk DEFINES += HAVE_ROUND LIBS += -dead-strip QMAKE_POST_LINK=strip qbrew.app/Contents/MacOS/qbrew # install into app bundle trans.path = qbrew.app/Contents/Resources/translations trans.files = translations/*.qm data.path = qbrew.app/Contents/Resources data.files = data/* pics/splash.png mac/application.icns mac/document.icns doc.path = qbrew.app/Contents/Resources/en.lproj doc.files = docs/book/*.html docs/primer/*.html misc.path = qbrew.app/Contents misc.files = mac/Info.plist mac/PkgInfo INSTALLS += trans data doc misc } unix:!macx { configure { DEFINES += $$(HAVEDEFS) target.path = $$(BINDIR) trans.path = $$(DATADIR)/translations data.path = $$(DATADIR) doc.path = $$(DOCDIR) } else { target.path = /usr/local/bin trans.path = /usr/local/share/qbrew/translations data.path = /usr/local/share/qbrew doc.path = /usr/local/share/doc/qbrew } trans.files = translations/*.qm data.files = data/* pics/splash.png doc.files = docs/book/*.html docs/primer/*.html README LICENSE INSTALLS += target trans data doc } RESOURCES = qbrew.qrc HEADERS = alcoholtool.h \ beerxmlreader.h \ configstate.h \ configure.h \ data.h \ databasetool.h \ datareader.h \ grain.h \ graindelegate.h \ grainmodel.h \ helpviewer.h \ hop.h \ hopdelegate.h \ hopmodel.h \ hydrometertool.h \ ingredientview.h \ misc.h \ miscdelegate.h \ miscmodel.h \ notepage.h \ qbrew.h \ quantity.h \ recipe.h \ recipereader.h \ resource.h \ style.h \ styledelegate.h \ stylemodel.h \ textprinter.h \ view.h SOURCES = alcoholtool.cpp \ beerxmlreader.cpp \ configure.cpp \ data.cpp \ databasetool.cpp \ datareader.cpp \ export.cpp \ grain.cpp \ graindelegate.cpp \ grainmodel.cpp \ helpviewer.cpp \ hop.cpp \ hopdelegate.cpp \ hopmodel.cpp \ hydrometertool.cpp \ ingredientview.cpp \ main.cpp \ misc.cpp \ miscdelegate.cpp \ miscmodel.cpp \ notepage.cpp \ qbrew.cpp \ quantity.cpp \ recipe.cpp \ recipereader.cpp \ style.cpp \ styledelegate.cpp \ stylemodel.cpp \ textprinter.cpp \ view.cpp FORMS = alcoholtool.ui \ calcconfig.ui \ databasetool.ui \ generalconfig.ui \ helpviewer.ui \ hydrometertool.ui \ ingredientpage.ui \ mainwindow.ui \ noteview.ui \ recipeconfig.ui \ view.ui TRANSLATIONS = translations/qbrew_de.ts qbrew-0.4.1/configure0000755000175100017510000001652511016417221013511 0ustar daviddavid#!/bin/sh # Copyright 2007-2008 David Johnson # Configuration script for use with qt/qmake projects. This configure # script is free software; the author gives unlimited permission to # copy, distribute and modify it. Portions of this script adapted # from the Qt configuration script. PACKAGE=qbrew PROFILE=qbrew.pro PREFIX=/usr/local QTMINVER=4.3.0 LOGFILE=config.log BUILDDIR=.config ### Messages ######################################################## usage() { cat </dev/null 2>&1; then MAKE=`which $mk` break fi done if [ -z "$MAKE" ] ; then echo "ERROR: cannot find 'make' or 'gmake'. Exiting" echo "ERROR: cannot find 'make' or 'gmake'. Exiting" >> $LOGFILE exit 1 fi fi } qmake_test() { echo -n "Checking for qmake..." # check QTDIR if [ -z "$QMAKE" ] ; then result=$QTDIR/bin/qmake if [ -x "$result" ] ; then QMAKE=$result fi fi # check PATH if [ -z "$QMAKE" ] ; then result=`which qmake 2>/dev/null` if [ -x "$result" ] ; then QMAKE=$result fi fi # check common rename if [ -z "$QMAKE" ] ; then result=`which qmake-q4 2>/dev/null` if [ -x "$result" ] ; then QMAKE=$result fi fi # check pkg-config if [ -z "$QMAKE" ] ; then result=`pkg-config QtCore --variable=exec_prefix 2>/dev/null` if [ ! -z "$result" ] ; then result=$result/bin/qmake if [ -x "$result" ] ; then QMAKE=$result fi fi fi if [ -z "$QMAKE" ] ; then echo "no!" echo "ERROR: unable to find the 'qmake' tool" echo "ERROR: unable to find the 'qmake' tool" >> $LOGFILE qt_info exit 1; fi echo "yes" echo "found qmake at $QMAKE" >> $LOGFILE } qtversion_test() { echo -n "Checking Qt version..." result=`$QMAKE -query QT_VERSION` if [ "$result" '<' "$QTMINVER" ] ; then echo "no!" echo "ERROR: incorrect Qt version found: $result" echo "ERROR: incorrect Qt version found: $result" >> $LOGFILE qt_info exit 1; fi echo "$result" echo "found Qt version $result" >> $LOGFILE } build_test() { echo -n "Checking if a simple Qt program builds..." cwd=`pwd` rm -rf $BUILDDIR mkdir -p $BUILDDIR cd $BUILDDIR # write it cat >config.pro <config.h < class Config : public QCoreApplication { public: Config(int &argc, char **argv); }; EOT cat >config.cpp < /dev/null $MAKE >>$LOGFILE 2>&1 result=$? cd $cwd rm -rf $BUILDDIR if [ "$result" != "0" ] ; then echo "no!" echo "ERROR: could not build a simple Qt program. See config.log" echo "ERROR: could not build a simple Qt program" >> $LOGFILE qt_info exit 1; fi echo "yes" echo "built simple Qt program" >> $LOGFILE } ### Parse options ################################################### parse_options() { while [ $# -gt 0 ] ; do case "$1" in --prefix=*) PREFIX="${1#--prefix=}" shift ;; --bindir=*) BINDIR="${1#--bindir=}" shift ;; --datadir=*) DATADIR="${1#--datadir=}" shift ;; --docdir=*) DOCDIR="${1#--docdir=}" shift ;; --qtdir=*) QTDIR="${1#--qtdir=}" shift ;; --spec=*) QMAKESPEC="${1#--spec=}" shift ;; --debug=*) ENABLE_DEBUG="${1#--debug=}" shift ;; --debug) ENABLE_DEBUG="yes" shift ;; --help) usage; exit ;; *) usage; exit 1 ;; esac done } ### Main ############################################################ echo "Configuring $PACKAGE package..." ### initialize SOURCEPATH=`dirname $0` SOURCEPATH=`(cd $SOURCEPATH; /bin/pwd)` BUILDDIR=$SOURCEPATH/$BUILDDIR LOGFILE=$SOURCEPATH/$LOGFILE if [ -e $LOGFILE ] ; then rm $LOGFILE ; fi echo "configure command: $0 $@" > $LOGFILE echo `date` >>$LOGFILE ### parse command options parse_options $@ QMAKE=${QMAKE:-} PREFIX=${PREFIX:-/usr/local} BINDIR=${BINDIR:-$PREFIX/bin} DATADIR=${DATADIR:-$PREFIX/share/$PACKAGE} DOCDIR=${DOCDIR:-$PREFIX/share/doc/$PACKAGE} if [ "$ENABLE_DEBUG" = "yes" ] ; then BUILDMODE="debug" else BUILDMODE="release" fi echo >> $LOGFILE echo PREFIX=$PREFIX >> $LOGFILE echo BINDIR=$BINDIR >> $LOGFILE echo DATADIR=$DATADIR >> $LOGFILE echo DOCDIR=$DOCDIR >> $LOGFILE echo QTDIR=$QTDIR >> $LOGFILE echo QMAKESPEC=$QMAKESPEC >> $LOGFILE echo ENABLE_DEBUG=$ENABLE_DEBUG >> $LOGFILE echo BUILDMODE=$BUILDMODE >> $LOGFILE echo >> $LOGFILE ### tests make_test qmake_test qtversion_test build_test ### export variables export BINDIR export DATADIR export DOCDIR if [ ! -z $QTDIR ] ; then export QTDIR fi if [ ! -z $QMAKESPEC ] ; then export QMAKESPEC fi ### create Makefile echo "Creating Makefile" $QMAKE -o $SOURCEPATH/Makefile "CONFIG+=$BUILDMODE configure" $PROFILE > /dev/null 2>> $LOGFILE if [ $? != "0" ] ; then echo "ERROR: could not create Makefile. See config.log" echo "ERROR: could not create Makefile." >> $LOGFILE echo "Command: $QMAKE -o $SOURCEPATH/Makefile \"CONFIG+=$BUILDMODE\" $PROFILE" >> $LOGFILE exit 1 fi ### finished echo echo "$PACKAGE configured successfully" echo "Relax, don't worry, have a homebrew!" echoqbrew-0.4.1/paleale.qbrew0000644000175100017510000000171011016417221014235 0ustar daviddavid SQA Pale Ale Joe Sixpack Light Malt Extract Crystal 40L Columbus Columbus Columbus Columbus London Pale Ale yeast qbrew-0.4.1/LICENSE0000644000175100017510000000272211016417221012601 0ustar daviddavidCopyright (c) 1999-2008 David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------ This product contains software written by Abe Kabakoff This product contains software written by Michal Palczewski qbrew-0.4.1/AUTHORS0000644000175100017510000000100511016417221012635 0ustar daviddavidAuthors ------- David Johnson Feature Contributors ----------------- Abe Kabakoff Michal Palczewski Bug Fix Contributors -------------------- Rob Hudson Kevin Pullin Lee Henderson Document Contributors --------------------- Stephen Lowrie Artwork Contributors -------------------- The KDE Project Translators ----------- Tobias Toedter qbrew-0.4.1/ChangeLog0000644000175100017510000000562311016417221013351 0ustar daviddavid05/16/2008 - Use streaming XML instead of DOM 05/04/2008 - Print preview, print improvements 10/05/2007 - Import bug fixes Version 0.4.0 ------------- 09/22/2007 - Added ingredient types 08/05/2007 - BeerXML import 07/25/2007 - I18N improvements 07/20/2007 - Printing improvements 05/30/2007 - Qt 4.3.0, various improvements 07/05/2006 - Calculation corrections 03/12/2006 - Ported to Qt 4 12/03/2005 - Fix column sorting (Lee Henderson) Version 0.3.5 ------------- 05/17/2005 - Updated style database to BJCP 2004 05/05/2005 - Added splashscreen 04/17/2005 - Added database editor tool 04/10/2005 - Minor refactoring 03/25/2005 - Export to HTML, improved printing Version 0.3.4 ------------- 09/04/2004 - Fixed ingredient sorting 08/29/2004 - Fixed fixed format spinboxes 08/25/2004 - Added autosave, autobackup 08/22/2004 - Improved configuration dialog 06/26/2004 - Improved gravity calculations (Michal Palczewski) 06/07/2004 - Embedded images 05/27/2004 - Added ABV tool (Michal Palczewski) 02/01/2004 - Dropped support for pre qt-3.2 versions Version 0.3.3 ------------- 07/08/2003 - Added in Morey color calculation as an option 03/05/2003 - Miscellaneous bug fixes Version 0.3.2 ------------- 03/01/2003 - added notes page for recipe and batch notes 03/01/2003 - converted document processing over to xslt 02/28/2003 - ingredient comboboxes remember their state 02/27/2003 - configure detects qt-3.1 11/17/2002 - improved toolbar and icons Version 0.3.1 ------------- 04/19/2002 - autoconf rework 04/12/2002 - win32 compatibility 04/09/2002 - Qt-3x compatibility Version 0.3.0 - Porter release ------------- 12/31/2001- - Metric/US conversions 10/13/2001 - Fixed installation directories 10/11/2001 - Rearchitecture Version 0.2.0 - Bitter release ------------- 02/21/2001 - Added Abe Kabakoff's hydrometer correction dialog 02/12/2001 - Changed over file formats to XML/DOM, kept some conversion routines Version 0.1.6 ------------- 02/09/2001 - Calculation and format fixes. Added sqa test procedure Version 0.1.5 ------------- 12/08/2000 - Code cleanup, reformat, and minor redesign. Version 0.1.4 ------------- 09/22/2000 - Added in Tinseth formula as an option 09/21/2000 - Bug fixes and build/configuration changes Version 0.1.3 ------------- 06/17/2000 - Bug fix for Qt-2.0.2 compatibility Version 0.1.2 ------------- 06/14/2000 - Added brewing primer 05/04/2000 - Started some code cleanup 04/25/2000 - Added Step widget style Version 0.1.1 ------------- 12/07/1999 - Minor bug fixes. Added units suffixes to spinboxes Version 0.1.0 - Amber release ------------- 11/20/1999 - declared nominally "feature complete" 11/08/1999 - calculations in progress 10/26/1999 - user interface finished 10/10/1999 - working on listview for ingredients 10/03/1999 - brewing data files coming along 09/25/1999 - basic infrastructure in place September 20th, 1999 - Project Start qbrew-0.4.1/README0000644000175100017510000000461511016417221012457 0ustar daviddavidQBrew, Version 0.4 ================== QBrew is a homebrewing recipe calculator for Unix, Macintosh and Windows. The QBrew homepage is at . Please read the INSTALL file for installation instructions. Additional documentation can be found in the source tree under 'docs'. Please see the AUTHORS file for a list of contributors, and the ChangeLog file for a list of changes since the previous release. QBrew is released under a very liberal license which reflects the author's wishes with regards to this software. No payment or registration is required, but the author would be extremely delighted to receive bottles filled with homebrew created with QBrew. Photos of such bottles will do in a pinch. If you wish to contribute to the further inebriation of the author, please send mail to requesting a physical address to send the bottles to. Cash or code contributions are, of course, always welcome, and will grease the wheels of QBrew development. ==================== Copyright (c) 1999-2008, David Johnson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This product contains software written by Abe Kabakoff This product contains software written by Michal Palczewski qbrew-0.4.1/stout.qbrew0000644000175100017510000000201711016417221014011 0ustar daviddavid SQA Imperial Stout Joe Sixpack American two-row British black patent Black Barley British chocolate malt Northern Brewer Northern Brewer Wyeast Irish 1084 qbrew-0.4.1/qbrew.qrc0000644000175100017510000000240511016417221013421 0ustar daviddavid pics/icons/back.png pics/icons/configure.png pics/icons/contents.png pics/icons/contexthelp.png pics/icons/down.png pics/icons/editcopy.png pics/icons/editcut.png pics/icons/editpaste.png pics/icons/exit.png pics/icons/fileexport.png pics/icons/filenew.png pics/icons/fileopen.png pics/icons/filepagesetup.png pics/icons/fileprint.png pics/icons/filesave.png pics/icons/filesaveas.png pics/icons/find.png pics/icons/forward.png pics/icons/gohome.png pics/icons/minus.png pics/icons/plus.png pics/icons/redo.png pics/icons/reload.png pics/icons/undo.png pics/icons/up.png pics/icons/viewzoomin.png pics/icons/viewzoomout.png pics/qbrew.png qbrew-0.4.1/TODO0000644000175100017510000000221511016417221012261 0ustar daviddavidNear and Long Term Future Plans for QBrew --------------------------------------------- Short Term ---------- Undo/redo framework Unit tests QHelpEngine Imperial units Option to use Plato instead of SG Session management Cut/copy/paste. At minimum copy the characteristics to clipboard. Additional fields: ingredient notes (also good for dryhopping) Update the database More than just "unit" for misc ingredients (Tsp, etc) Possibility of adding a list of fermentation times. Different spinbox increments depending on units used (0.25kg is too much) Medium Term ----------- Unit tests More detailed batch notes, or batch section (measured OG, FG, etc) "Contribution" column to grain page ((quantity * extract) / batch_size) Calculate mash efficiency and store for later use Long Term --------- Inventory management Multiple recipes in model SPECIAL OFFER!!! Hey kids! You too can participate in the exiting world of software development! Just collect five proof-of-purchase stickers from the back of QBrew boxes and send them in with your favorite feature request. If YOUR feature is selected, your next version of QBrew with YOUR feature is absolutely free! qbrew-0.4.1/INSTALL0000644000175100017510000000526611016417221012633 0ustar daviddavidQBrew Installation =================== These instructions are for building and installing QBrew from source code. QBrew requires the Qt 4.3 or greater development libraries. These can be found at . Some Linux distributions separate Qt into separate runtime and development packages. If this is your case, Make sure you have both. QBrew uses the qmake tool to generate makefiles, and thus depends on a properly configured Qt installation. Note that I am not providing installation support for QBrew, and will consider such a submission to be a bug report as opposed to a support request. I can be contacted at . Sincerely, David Johnson Unix Build ========== 0) Make sure the prerequisite Qt library is installed. 1) Unarchive the source code package, and change into its directory. 2) Type './configure --help", and peruse the configure options available. It may be necessary to use one or more of these options on your system. 3) Type './configure' to configure the package for your system. If you're using `csh', you might need to type `sh ./configure' instead. 4) Type 'make' to compile the package. 5) Login, su or sudo to root. This is not necessary if you configured the package to install to your home directory. 6) Type 'make install' to install the program and documentation. 7) Type `qbrew' to run the program. Mac OSX Build ============= 0) Make sure the prerequisite Qt library is installed. 1) Unarchive the source code package, and change into its directory. 2) Set Qt related environment variables (assumes bash shell): QTDIR=/Developer/qt (or as appropriate) QMAKESPEC=macx-g++ PATH=$PATH:$QTDIR/bin export QTDIR QMAKESPEC PATH 3) Build the package: qmake -o Makefile qbrew.pro make 4) Populate the application bundle make install 5) You may wish to include the Qt runtime with the application bundle. Please see the "Deploying on Application on Qt/Mac" article for more information. A deployment script is included in the mac directory, that may be useful. 6) Now move the application bundle to your desired location mv qbrew.app ~/Desktop Windows Build ============= 0) Make sure the prerequisite Qt library is installed. 1) Unarchive the source code package, and change into its directory. 2) Build the package: qmake qbrew.pro make (or nmake) 4) Place into a final installation directory, with the following minimum structure: qbrew.exe splash.png datafile doc\html\* (all html help files) translations\* (all qm files) 5) You may also wish to include the Qt DLL and other runtime libraries in the installation directory.