summaryrefslogtreecommitdiffstats
path: root/krecipes/src
diff options
context:
space:
mode:
authorSlávek Banko <slavek.banko@axis.cz>2013-07-31 14:57:12 +0200
committerSlávek Banko <slavek.banko@axis.cz>2013-07-31 14:57:12 +0200
commit1a79d6f5bfce52feb71c264d1b63d78a3da584c4 (patch)
tree740b3faa8a9acff0a524733ce5dc5b036bc53969 /krecipes/src
downloadkrecipes-1a79d6f5bfce52feb71c264d1b63d78a3da584c4.tar.gz
krecipes-1a79d6f5bfce52feb71c264d1b63d78a3da584c4.zip
Initial import of krecipes 1.0-beta2
Diffstat (limited to 'krecipes/src')
-rw-r--r--krecipes/src/Makefile.am57
-rw-r--r--krecipes/src/backends/Makefile.am42
-rw-r--r--krecipes/src/backends/MySQL/Makefile.am18
-rw-r--r--krecipes/src/backends/MySQL/mysqlrecipedb.cpp554
-rw-r--r--krecipes/src/backends/MySQL/mysqlrecipedb.h56
-rw-r--r--krecipes/src/backends/PostgreSQL/Makefile.am17
-rw-r--r--krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp560
-rw-r--r--krecipes/src/backends/PostgreSQL/psqlrecipedb.h64
-rw-r--r--krecipes/src/backends/SQLite/Makefile.am19
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/Makefile.am17
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp171
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h59
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp197
-rw-r--r--krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h142
-rw-r--r--krecipes/src/backends/SQLite/literecipedb.cpp1034
-rw-r--r--krecipes/src/backends/SQLite/literecipedb.h60
-rw-r--r--krecipes/src/backends/SQLite/qsql_sqlite.cpp459
-rw-r--r--krecipes/src/backends/SQLite/qsql_sqlite.h53
-rw-r--r--krecipes/src/backends/progressinterface.cpp79
-rw-r--r--krecipes/src/backends/progressinterface.h70
-rw-r--r--krecipes/src/backends/qsqlrecipedb.cpp2775
-rw-r--r--krecipes/src/backends/qsqlrecipedb.h228
-rw-r--r--krecipes/src/backends/recipedb.cpp989
-rw-r--r--krecipes/src/backends/recipedb.h397
-rw-r--r--krecipes/src/backends/searchparameters.h63
-rw-r--r--krecipes/src/backends/usda_ingredient_data.h486
-rw-r--r--krecipes/src/backends/usda_property_data.h76
-rw-r--r--krecipes/src/backends/usda_unit_data.h104
-rw-r--r--krecipes/src/convert_sqlite3.cpp121
-rw-r--r--krecipes/src/convert_sqlite3.h35
-rw-r--r--krecipes/src/datablocks/Makefile.am20
-rw-r--r--krecipes/src/datablocks/categorytree.cpp133
-rw-r--r--krecipes/src/datablocks/categorytree.h61
-rw-r--r--krecipes/src/datablocks/constraintlist.cpp11
-rw-r--r--krecipes/src/datablocks/constraintlist.h47
-rw-r--r--krecipes/src/datablocks/element.cpp43
-rw-r--r--krecipes/src/datablocks/element.h41
-rw-r--r--krecipes/src/datablocks/elementlist.cpp102
-rw-r--r--krecipes/src/datablocks/elementlist.h43
-rw-r--r--krecipes/src/datablocks/ingredient.cpp95
-rw-r--r--krecipes/src/datablocks/ingredient.h63
-rw-r--r--krecipes/src/datablocks/ingredientlist.cpp272
-rw-r--r--krecipes/src/datablocks/ingredientlist.h61
-rw-r--r--krecipes/src/datablocks/ingredientproperty.cpp32
-rw-r--r--krecipes/src/datablocks/ingredientproperty.h37
-rw-r--r--krecipes/src/datablocks/ingredientpropertylist.cpp53
-rw-r--r--krecipes/src/datablocks/ingredientpropertylist.h30
-rw-r--r--krecipes/src/datablocks/kreborder.cpp17
-rw-r--r--krecipes/src/datablocks/kreborder.h29
-rw-r--r--krecipes/src/datablocks/mixednumber.cpp289
-rw-r--r--krecipes/src/datablocks/mixednumber.h126
-rw-r--r--krecipes/src/datablocks/rating.cpp86
-rw-r--r--krecipes/src/datablocks/rating.h55
-rw-r--r--krecipes/src/datablocks/recipe.cpp53
-rw-r--r--krecipes/src/datablocks/recipe.h71
-rw-r--r--krecipes/src/datablocks/recipelist.cpp18
-rw-r--r--krecipes/src/datablocks/recipelist.h28
-rw-r--r--krecipes/src/datablocks/unit.cpp75
-rw-r--r--krecipes/src/datablocks/unit.h45
-rw-r--r--krecipes/src/datablocks/unitratio.cpp30
-rw-r--r--krecipes/src/datablocks/unitratio.h37
-rw-r--r--krecipes/src/datablocks/unitratiolist.cpp34
-rw-r--r--krecipes/src/datablocks/unitratiolist.h33
-rw-r--r--krecipes/src/datablocks/weight.cpp14
-rw-r--r--krecipes/src/datablocks/weight.h41
-rw-r--r--krecipes/src/dialogs/Makefile.am38
-rw-r--r--krecipes/src/dialogs/advancedsearchdialog.cpp933
-rw-r--r--krecipes/src/dialogs/advancedsearchdialog.h179
-rw-r--r--krecipes/src/dialogs/authorsdialog.cpp66
-rw-r--r--krecipes/src/dialogs/authorsdialog.h51
-rw-r--r--krecipes/src/dialogs/borderdialog.cpp261
-rw-r--r--krecipes/src/dialogs/borderdialog.h75
-rw-r--r--krecipes/src/dialogs/categorieseditordialog.cpp67
-rw-r--r--krecipes/src/dialogs/categorieseditordialog.h56
-rw-r--r--krecipes/src/dialogs/conversiondialog.cpp166
-rw-r--r--krecipes/src/dialogs/conversiondialog.h62
-rw-r--r--krecipes/src/dialogs/createcategorydialog.cpp80
-rw-r--r--krecipes/src/dialogs/createcategorydialog.h51
-rw-r--r--krecipes/src/dialogs/createelementdialog.cpp48
-rw-r--r--krecipes/src/dialogs/createelementdialog.h40
-rw-r--r--krecipes/src/dialogs/createingredientweightdialog.cpp124
-rw-r--r--krecipes/src/dialogs/createingredientweightdialog.h60
-rw-r--r--krecipes/src/dialogs/createpropertydialog.cpp69
-rw-r--r--krecipes/src/dialogs/createpropertydialog.h56
-rw-r--r--krecipes/src/dialogs/createunitdialog.cpp116
-rw-r--r--krecipes/src/dialogs/createunitdialog.h52
-rw-r--r--krecipes/src/dialogs/dbimportdialog.cpp220
-rw-r--r--krecipes/src/dialogs/dbimportdialog.h76
-rw-r--r--krecipes/src/dialogs/dependanciesdialog.cpp100
-rw-r--r--krecipes/src/dialogs/dependanciesdialog.h60
-rw-r--r--krecipes/src/dialogs/dietviewdialog.cpp137
-rw-r--r--krecipes/src/dialogs/dietviewdialog.h45
-rw-r--r--krecipes/src/dialogs/dietwizarddialog.cpp799
-rw-r--r--krecipes/src/dialogs/dietwizarddialog.h222
-rw-r--r--krecipes/src/dialogs/editratingdialog.cpp243
-rw-r--r--krecipes/src/dialogs/editratingdialog.h77
-rw-r--r--krecipes/src/dialogs/ingredientgroupsdialog.cpp65
-rw-r--r--krecipes/src/dialogs/ingredientgroupsdialog.h33
-rw-r--r--krecipes/src/dialogs/ingredientmatcherdialog.cpp353
-rw-r--r--krecipes/src/dialogs/ingredientmatcherdialog.h156
-rw-r--r--krecipes/src/dialogs/ingredientparserdialog.cpp299
-rw-r--r--krecipes/src/dialogs/ingredientparserdialog.h65
-rw-r--r--krecipes/src/dialogs/ingredientsdialog.cpp702
-rw-r--r--krecipes/src/dialogs/ingredientsdialog.h93
-rw-r--r--krecipes/src/dialogs/pagesetupdialog.cpp310
-rw-r--r--krecipes/src/dialogs/pagesetupdialog.h72
-rw-r--r--krecipes/src/dialogs/prepmethodsdialog.cpp67
-rw-r--r--krecipes/src/dialogs/prepmethodsdialog.h53
-rw-r--r--krecipes/src/dialogs/propertiesdialog.cpp77
-rw-r--r--krecipes/src/dialogs/propertiesdialog.h47
-rw-r--r--krecipes/src/dialogs/recipeimportdialog.cpp185
-rw-r--r--krecipes/src/dialogs/recipeimportdialog.h80
-rw-r--r--krecipes/src/dialogs/recipeinputdialog.cpp1641
-rw-r--r--krecipes/src/dialogs/recipeinputdialog.h228
-rw-r--r--krecipes/src/dialogs/recipeprintpreview.cpp62
-rw-r--r--krecipes/src/dialogs/recipeprintpreview.h41
-rw-r--r--krecipes/src/dialogs/recipeviewdialog.cpp167
-rw-r--r--krecipes/src/dialogs/recipeviewdialog.h77
-rw-r--r--krecipes/src/dialogs/refineshoppinglistdialog.cpp206
-rw-r--r--krecipes/src/dialogs/refineshoppinglistdialog.h66
-rw-r--r--krecipes/src/dialogs/resizerecipedialog.cpp191
-rw-r--r--krecipes/src/dialogs/resizerecipedialog.h69
-rw-r--r--krecipes/src/dialogs/selectauthorsdialog.cpp181
-rw-r--r--krecipes/src/dialogs/selectauthorsdialog.h66
-rw-r--r--krecipes/src/dialogs/selectcategoriesdialog.cpp104
-rw-r--r--krecipes/src/dialogs/selectcategoriesdialog.h57
-rw-r--r--krecipes/src/dialogs/selectpropertydialog.cpp123
-rw-r--r--krecipes/src/dialogs/selectpropertydialog.h60
-rw-r--r--krecipes/src/dialogs/selectrecipedialog.cpp257
-rw-r--r--krecipes/src/dialogs/selectrecipedialog.h99
-rw-r--r--krecipes/src/dialogs/selectunitdialog.cpp77
-rw-r--r--krecipes/src/dialogs/selectunitdialog.h51
-rw-r--r--krecipes/src/dialogs/setupdisplay.cpp602
-rw-r--r--krecipes/src/dialogs/setupdisplay.h148
-rw-r--r--krecipes/src/dialogs/shoppinglistdialog.cpp267
-rw-r--r--krecipes/src/dialogs/shoppinglistdialog.h76
-rw-r--r--krecipes/src/dialogs/shoppinglistviewdialog.cpp102
-rw-r--r--krecipes/src/dialogs/shoppinglistviewdialog.h46
-rw-r--r--krecipes/src/dialogs/similarcategoriesdialog.cpp383
-rw-r--r--krecipes/src/dialogs/similarcategoriesdialog.h84
-rw-r--r--krecipes/src/dialogs/unitsdialog.cpp211
-rw-r--r--krecipes/src/dialogs/unitsdialog.h65
-rw-r--r--krecipes/src/dialogs/usdadatadialog.cpp200
-rw-r--r--krecipes/src/dialogs/usdadatadialog.h47
-rw-r--r--krecipes/src/exporters/Makefile.am13
-rw-r--r--krecipes/src/exporters/baseexporter.cpp172
-rw-r--r--krecipes/src/exporters/baseexporter.h95
-rw-r--r--krecipes/src/exporters/cookmlexporter.cpp122
-rw-r--r--krecipes/src/exporters/cookmlexporter.h38
-rw-r--r--krecipes/src/exporters/htmlbookexporter.cpp160
-rw-r--r--krecipes/src/exporters/htmlbookexporter.h52
-rw-r--r--krecipes/src/exporters/htmlexporter.cpp570
-rw-r--r--krecipes/src/exporters/htmlexporter.h78
-rw-r--r--krecipes/src/exporters/kreexporter.cpp265
-rw-r--r--krecipes/src/exporters/kreexporter.h51
-rw-r--r--krecipes/src/exporters/mmfexporter.cpp220
-rw-r--r--krecipes/src/exporters/mmfexporter.h51
-rw-r--r--krecipes/src/exporters/plaintextexporter.cpp176
-rw-r--r--krecipes/src/exporters/plaintextexporter.h42
-rw-r--r--krecipes/src/exporters/recipemlexporter.cpp195
-rw-r--r--krecipes/src/exporters/recipemlexporter.h45
-rw-r--r--krecipes/src/exporters/rezkonvexporter.cpp322
-rw-r--r--krecipes/src/exporters/rezkonvexporter.h51
-rw-r--r--krecipes/src/hi128-app-krecipes.pngbin0 -> 13215 bytes
-rw-r--r--krecipes/src/hi16-app-krecipes.pngbin0 -> 852 bytes
-rw-r--r--krecipes/src/hi22-app-krecipes.pngbin0 -> 1275 bytes
-rw-r--r--krecipes/src/hi32-app-krecipes.pngbin0 -> 2055 bytes
-rw-r--r--krecipes/src/hi48-app-krecipes.pngbin0 -> 3586 bytes
-rw-r--r--krecipes/src/hi64-app-krecipes.pngbin0 -> 5301 bytes
-rw-r--r--krecipes/src/image.h1123
-rw-r--r--krecipes/src/importers/Makefile.am15
-rw-r--r--krecipes/src/importers/baseimporter.cpp399
-rw-r--r--krecipes/src/importers/baseimporter.h126
-rw-r--r--krecipes/src/importers/kredbimporter.cpp67
-rw-r--r--krecipes/src/importers/kredbimporter.h38
-rw-r--r--krecipes/src/importers/kreimporter.cpp309
-rw-r--r--krecipes/src/importers/kreimporter.h54
-rw-r--r--krecipes/src/importers/mmfimporter.cpp336
-rw-r--r--krecipes/src/importers/mmfimporter.h65
-rw-r--r--krecipes/src/importers/mx2importer.cpp186
-rw-r--r--krecipes/src/importers/mx2importer.h46
-rw-r--r--krecipes/src/importers/mxpimporter.cpp382
-rw-r--r--krecipes/src/importers/mxpimporter.h45
-rw-r--r--krecipes/src/importers/nycgenericimporter.cpp196
-rw-r--r--krecipes/src/importers/nycgenericimporter.h44
-rw-r--r--krecipes/src/importers/recipemlimporter.cpp376
-rw-r--r--krecipes/src/importers/recipemlimporter.h53
-rw-r--r--krecipes/src/importers/rezkonvimporter.cpp291
-rw-r--r--krecipes/src/importers/rezkonvimporter.h42
-rw-r--r--krecipes/src/klomanager.cpp218
-rw-r--r--krecipes/src/klomanager.h59
-rw-r--r--krecipes/src/krecipes.cpp703
-rw-r--r--krecipes/src/krecipes.desktop66
-rw-r--r--krecipes/src/krecipes.h140
-rw-r--r--krecipes/src/krecipes.lsm14
-rw-r--r--krecipes/src/krecipesdbiface.h26
-rw-r--r--krecipes/src/krecipesiface.h31
-rw-r--r--krecipes/src/krecipesui.rc27
-rw-r--r--krecipes/src/krecipesview.cpp939
-rw-r--r--krecipes/src/krecipesview.h215
-rw-r--r--krecipes/src/krepagelayout.cpp242
-rw-r--r--krecipes/src/krepagelayout.h257
-rwxr-xr-xkrecipes/src/kstartuplogo.cpp70
-rwxr-xr-xkrecipes/src/kstartuplogo.h49
-rw-r--r--krecipes/src/main.cpp81
-rw-r--r--krecipes/src/mmdata.h65
-rw-r--r--krecipes/src/pref.cpp678
-rw-r--r--krecipes/src/pref.h182
-rw-r--r--krecipes/src/profiling.h34
-rw-r--r--krecipes/src/propertycalculator.cpp131
-rw-r--r--krecipes/src/propertycalculator.h24
-rw-r--r--krecipes/src/recipeactionshandler.cpp475
-rw-r--r--krecipes/src/recipeactionshandler.h119
-rw-r--r--krecipes/src/recipefilter.cpp154
-rw-r--r--krecipes/src/recipefilter.h39
-rw-r--r--krecipes/src/setupwizard.cpp851
-rw-r--r--krecipes/src/setupwizard.h224
-rw-r--r--krecipes/src/shoppingcalculator.cpp78
-rw-r--r--krecipes/src/shoppingcalculator.h23
-rw-r--r--krecipes/src/tests/Makefile.am19
-rw-r--r--krecipes/src/tests/checks.h181
-rw-r--r--krecipes/src/tests/exportertest.h42
-rw-r--r--krecipes/src/tests/importertest.h45
-rw-r--r--krecipes/src/tests/kretest.cpp191
-rw-r--r--krecipes/src/tests/kretest.txt409
-rw-r--r--krecipes/src/tests/mmftest.cpp139
-rw-r--r--krecipes/src/tests/mmftest.txt49
-rw-r--r--krecipes/src/tests/mx2test.cpp72
-rw-r--r--krecipes/src/tests/mx2test.txt0
-rw-r--r--krecipes/src/tests/mxptest.cpp72
-rw-r--r--krecipes/src/tests/mxptest.txt25
-rw-r--r--krecipes/src/tests/nyctest.cpp100
-rw-r--r--krecipes/src/tests/nyctest.txt57
-rw-r--r--krecipes/src/tests/recipemltest.cpp158
-rw-r--r--krecipes/src/tests/recipemltest.txt249
-rw-r--r--krecipes/src/tests/rezkonvtest.cpp155
-rw-r--r--krecipes/src/tests/rezkonvtest.txt55
-rw-r--r--krecipes/src/tests/test_photo.jpgbin0 -> 3369 bytes
-rw-r--r--krecipes/src/widgets/Makefile.am28
-rw-r--r--krecipes/src/widgets/amountunitinput.cpp68
-rw-r--r--krecipes/src/widgets/amountunitinput.h59
-rw-r--r--krecipes/src/widgets/authorlistview.cpp275
-rw-r--r--krecipes/src/widgets/authorlistview.h107
-rw-r--r--krecipes/src/widgets/categorycombobox.cpp182
-rw-r--r--krecipes/src/widgets/categorycombobox.h59
-rw-r--r--krecipes/src/widgets/categorylistview.cpp637
-rw-r--r--krecipes/src/widgets/categorylistview.h288
-rw-r--r--krecipes/src/widgets/conversiontable.cpp426
-rw-r--r--krecipes/src/widgets/conversiontable.h101
-rw-r--r--krecipes/src/widgets/criteriacombobox.cpp49
-rw-r--r--krecipes/src/widgets/criteriacombobox.h41
-rw-r--r--krecipes/src/widgets/dblistviewbase.cpp334
-rw-r--r--krecipes/src/widgets/dblistviewbase.h89
-rw-r--r--krecipes/src/widgets/fractioninput.cpp121
-rw-r--r--krecipes/src/widgets/fractioninput.h62
-rw-r--r--krecipes/src/widgets/headercombobox.cpp42
-rw-r--r--krecipes/src/widgets/headercombobox.h34
-rw-r--r--krecipes/src/widgets/headerlistview.cpp196
-rw-r--r--krecipes/src/widgets/headerlistview.h70
-rw-r--r--krecipes/src/widgets/inglistviewitem.cpp225
-rw-r--r--krecipes/src/widgets/inglistviewitem.h82
-rw-r--r--krecipes/src/widgets/ingredientcombobox.cpp188
-rw-r--r--krecipes/src/widgets/ingredientcombobox.h58
-rw-r--r--krecipes/src/widgets/ingredientinputwidget.cpp542
-rw-r--r--krecipes/src/widgets/ingredientinputwidget.h134
-rw-r--r--krecipes/src/widgets/ingredientlistview.cpp287
-rw-r--r--krecipes/src/widgets/ingredientlistview.h119
-rw-r--r--krecipes/src/widgets/kdateedit.cpp388
-rw-r--r--krecipes/src/widgets/kdateedit.h139
-rw-r--r--krecipes/src/widgets/kdatepickerpopup.cpp123
-rw-r--r--krecipes/src/widgets/kdatepickerpopup.h102
-rw-r--r--krecipes/src/widgets/krelistview.cpp116
-rw-r--r--krecipes/src/widgets/krelistview.h65
-rw-r--r--krecipes/src/widgets/kremenu.cpp639
-rw-r--r--krecipes/src/widgets/kremenu.h170
-rw-r--r--krecipes/src/widgets/kreruler.cpp1049
-rw-r--r--krecipes/src/widgets/kreruler.h366
-rw-r--r--krecipes/src/widgets/kretextedit.cpp183
-rw-r--r--krecipes/src/widgets/kretextedit.h52
-rw-r--r--krecipes/src/widgets/kwidgetlistbox.cpp210
-rw-r--r--krecipes/src/widgets/kwidgetlistbox.h71
-rw-r--r--krecipes/src/widgets/paneldeco.cpp181
-rw-r--r--krecipes/src/widgets/paneldeco.h85
-rw-r--r--krecipes/src/widgets/prepmethodcombobox.cpp186
-rw-r--r--krecipes/src/widgets/prepmethodcombobox.h48
-rw-r--r--krecipes/src/widgets/prepmethodlistview.cpp189
-rw-r--r--krecipes/src/widgets/prepmethodlistview.h70
-rw-r--r--krecipes/src/widgets/propertylistview.cpp289
-rw-r--r--krecipes/src/widgets/propertylistview.h203
-rw-r--r--krecipes/src/widgets/ratingdisplaywidget.ui239
-rw-r--r--krecipes/src/widgets/ratingwidget.cpp164
-rw-r--r--krecipes/src/widgets/ratingwidget.h63
-rw-r--r--krecipes/src/widgets/recipelistview.cpp447
-rw-r--r--krecipes/src/widgets/recipelistview.h166
-rw-r--r--krecipes/src/widgets/unitcombobox.cpp145
-rw-r--r--krecipes/src/widgets/unitcombobox.h52
-rw-r--r--krecipes/src/widgets/unitlistview.cpp369
-rw-r--r--krecipes/src/widgets/unitlistview.h76
-rw-r--r--krecipes/src/widgets/weightinput.cpp63
-rw-r--r--krecipes/src/widgets/weightinput.h42
300 files changed, 51363 insertions, 0 deletions
diff --git a/krecipes/src/Makefile.am b/krecipes/src/Makefile.am
new file mode 100644
index 0000000..ed4b0ef
--- /dev/null
+++ b/krecipes/src/Makefile.am
@@ -0,0 +1,57 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+bin_PROGRAMS = krecipes
+
+SUBDIRS = backends importers widgets dialogs exporters datablocks tests
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/backends -I$(srcdir)/backends/SQLite $(all_includes)
+
+# the library search path.
+krecipes_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+
+# Check for optional libs
+if link_lib_SQLITE
+qsqlite_libadds = backends/SQLite/libkrecsqlite.la $(SQLITE_LIB)
+endif
+
+if link_lib_MYSQL
+mysql_libadds =backends/MySQL/libkrecmysql.la
+endif
+
+if link_lib_POSTGRESQL
+psql_libadds =backends/PostgreSQL/libkrecpsql.la
+endif
+
+# the shared libraries to link against.
+krecipes_LDADD = \
+ backends/libkrecipesdbs.la exporters/libkrecipesexporters.la \
+ importers/libkrecipesimporters.la widgets/libkrecipeswidgets.la dialogs/libkrecipesdialogs.la \
+ widgets/libkrecipeswidgets.la datablocks/libdatablocks.la \
+ $(qsqlite_libadds) $(mysql_libadds) $(psql_libadds) $(LIB_KHTML) $(LIB_KSPELL)
+
+# which sources should be compiled for krecipes
+krecipes_SOURCES = \
+ main.cpp krecipes.cpp krecipesview.cpp pref.cpp \
+ krecipesiface.skel krecipesdbiface.skel \
+ propertycalculator.cpp setupwizard.cpp \
+ shoppingcalculator.cpp kstartuplogo.cpp \
+ recipeactionshandler.cpp \
+ recipefilter.cpp \
+ convert_sqlite3.cpp klomanager.cpp
+
+# let automoc handle all of the meta source files (moc)
+METASOURCES = AUTO
+
+KDE_ICON = krecipes
+
+# this is where the kdelnk file will go
+kdelnkdir = $(kde_appsdir)/Utilities
+kdelnk_DATA = krecipes.desktop
+
+# this is where the XML-GUI resource file goes
+rcdir = $(kde_datadir)/krecipes
+rc_DATA = krecipesui.rc
diff --git a/krecipes/src/backends/Makefile.am b/krecipes/src/backends/Makefile.am
new file mode 100644
index 0000000..9ef190d
--- /dev/null
+++ b/krecipes/src/backends/Makefile.am
@@ -0,0 +1,42 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. $(all_includes)
+
+# Check for optional database libs
+
+
+if link_lib_SQLITE
+sqlite_subdirs=SQLite
+sqlite_libs=SQLite/libkrecsqlite.la
+endif
+
+if link_lib_MYSQL
+mysql_subdirs=MySQL
+mysql_libs=MySQL/libkrecmysql.la
+endif
+
+if link_lib_POSTGRESQL
+psql_subdirs=PostgreSQL
+psql_libs=PostgreSQL/libkrecpsql.la
+endif
+
+# Optional subdirectories
+
+SUBDIRS=$(sqlite_subdirs) $(mysql_subdirs) $(psql_subdirs)
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecipesdbs.la
+libkrecipesdbs_la_SOURCES=recipedb.cpp qsqlrecipedb.cpp progressinterface.cpp
+libkrecipesdbs_la_METASOURCES=AUTO
+
+libkrecipesdbs_la_LIBADD= $(mysql_libs) $(sqlite_libs) $(psql_libs)
+
+#the library search path.
+libkrecipesdbs_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+#install the following headers
+# include_HEADERS = recipedb.h
diff --git a/krecipes/src/backends/MySQL/Makefile.am b/krecipes/src/backends/MySQL/Makefile.am
new file mode 100644
index 0000000..193496e
--- /dev/null
+++ b/krecipes/src/backends/MySQL/Makefile.am
@@ -0,0 +1,18 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../.. $(all_includes)
+
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecmysql.la
+libkrecmysql_la_SOURCES=mysqlrecipedb.cpp
+libkrecmysql_la_METASOURCES=AUTO
+
+
+#the library search path.
+libkrecmysql_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
diff --git a/krecipes/src/backends/MySQL/mysqlrecipedb.cpp b/krecipes/src/backends/MySQL/mysqlrecipedb.cpp
new file mode 100644
index 0000000..151954e
--- /dev/null
+++ b/krecipes/src/backends/MySQL/mysqlrecipedb.cpp
@@ -0,0 +1,554 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "mysqlrecipedb.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kglobal.h>
+
+MySQLRecipeDB::MySQLRecipeDB( const QString &host, const QString &user, const QString &pass, const QString &DBname, int port ) : QSqlRecipeDB( host, user, pass, DBname, port )
+{}
+
+MySQLRecipeDB::~MySQLRecipeDB()
+{}
+
+void MySQLRecipeDB::createDB()
+{
+ QString real_db_name = database->databaseName();
+
+ //we have to be connected to some database in order to create the Krecipes database
+ //so long as the permissions given are allowed access to "mysql', this works
+ database->setDatabaseName( "mysql" );
+ if ( database->open() ) {
+ // Create the Database (Note: needs permissions)
+ //FIXME: I've noticed certain characters cause this to fail (such as '-'). Somehow let the user know.
+ QSqlQuery query( QString( "CREATE DATABASE %1" ).arg( real_db_name ), database );
+ if ( !query.isActive() )
+ kdDebug() << "create query failed: " << database->lastError().databaseText() << endl;
+
+ database->close();
+ }
+ else
+ kdDebug() << "create open failed: " << database->lastError().databaseText() << endl;
+
+ database->setDatabaseName( real_db_name );
+}
+
+QStringList MySQLRecipeDB::backupCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "MySQLDumpPath", "mysqldump" )<<"-q";
+
+ QString pass = config->readEntry("Password", QString::null);
+ if ( !pass.isEmpty() )
+ command<<"-p"+pass;
+
+ QString user = config->readEntry("Username", QString::null);
+ command<<"-u"+user;
+
+ command<<"-h"+config->readEntry("Host", "localhost");
+
+ int port = config->readNumEntry("Port", 0);
+ if ( port > 0 )
+ command<<"-P"+QString::number(port);
+
+ command<<database->databaseName();
+ return command;
+}
+
+QStringList MySQLRecipeDB::restoreCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "MySQLPath", "mysql" );
+
+ QString pass = config->readEntry("Password", QString::null);
+ if ( !pass.isEmpty() )
+ command<<"-p"+pass;
+
+ QString user = config->readEntry("Username", QString::null);
+ command<<"-u"+user;
+
+ int port = config->readNumEntry("Port", 0);
+ if ( port > 0 )
+ command<<"-P"+QString::number(port);
+
+ command<<"-h"+config->readEntry("Host", "localhost");
+
+ command<<database->databaseName();
+ return command;
+}
+
+void MySQLRecipeDB::createTable( const QString &tableName )
+{
+
+ QStringList commands;
+
+ if ( tableName == "recipes" )
+ commands << QString( "CREATE TABLE recipes (id INTEGER NOT NULL AUTO_INCREMENT,title VARCHAR(%1), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id int(11) DEFAULT '-1', instructions TEXT, photo BLOB, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP, PRIMARY KEY (id));" ).arg( maxRecipeTitleLength() );
+
+ else if ( tableName == "ingredients" )
+ commands << QString( "CREATE TABLE ingredients (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxIngredientNameLength() );
+
+ else if ( tableName == "ingredient_list" )
+ commands << "CREATE TABLE ingredient_list (id INTEGER NOT NULL AUTO_INCREMENT, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, substitute_for INTEGER, PRIMARY KEY(id), INDEX ridil_index(recipe_id), INDEX iidil_index(ingredient_id), INDEX gidil_index(group_id))";
+
+ else if ( tableName == "unit_list" )
+ commands << "CREATE TABLE unit_list (ingredient_id INTEGER, unit_id INTEGER);";
+
+ else if ( tableName == "units" )
+ commands << QString( "CREATE TABLE units (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), name_abbrev VARCHAR(%2), plural VARCHAR(%3), plural_abbrev VARCHAR(%4), type INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (id));" )
+ .arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() );
+
+ else if ( tableName == "prep_methods" )
+ commands << QString( "CREATE TABLE prep_methods (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxPrepMethodNameLength() );
+
+ else if ( tableName == "prep_method_list" )
+ commands << "CREATE TABLE prep_method_list (ingredient_list_id int(11) NOT NULL,prep_method_id int(11) NOT NULL, order_index int(11), INDEX iid_index (ingredient_list_id), INDEX pid_index (prep_method_id));";
+
+ else if ( tableName == "ingredient_info" )
+ commands << "CREATE TABLE ingredient_info (ingredient_id INTEGER, property_id INTEGER, amount FLOAT, per_units INTEGER);";
+
+ else if ( tableName == "ingredient_properties" )
+ commands << "CREATE TABLE ingredient_properties (id INTEGER NOT NULL AUTO_INCREMENT,name VARCHAR(20), units VARCHAR(20), PRIMARY KEY (id));";
+
+ else if ( tableName == "ingredient_weights" )
+ commands << "CREATE TABLE ingredient_weights (id INTEGER NOT NULL AUTO_INCREMENT, ingredient_id INTEGER NOT NULL, amount FLOAT, unit_id INTEGER, weight FLOAT, weight_unit_id INTEGER, prep_method_id INTEGER, PRIMARY KEY (id), INDEX(ingredient_id), INDEX(unit_id), INDEX(weight_unit_id), INDEX(prep_method_id) );";
+
+ else if ( tableName == "units_conversion" )
+ commands << "CREATE TABLE units_conversion (unit1_id INTEGER, unit2_id INTEGER, ratio FLOAT);";
+
+ else if ( tableName == "categories" )
+ commands << QString( "CREATE TABLE categories (id int(11) NOT NULL auto_increment, name varchar(%1) default NULL, parent_id int(11) NOT NULL default -1, PRIMARY KEY (id), INDEX parent_id_index(parent_id));" ).arg( maxCategoryNameLength() );
+
+ else if ( tableName == "category_list" )
+ commands << "CREATE TABLE category_list (recipe_id int(11) NOT NULL,category_id int(11) NOT NULL, INDEX rid_index (recipe_id), INDEX cid_index (category_id));";
+
+ else if ( tableName == "authors" )
+ commands << QString( "CREATE TABLE authors (id int(11) NOT NULL auto_increment, name varchar(%1) default NULL,PRIMARY KEY (id));" ).arg( maxAuthorNameLength() );
+
+ else if ( tableName == "author_list" )
+ commands << "CREATE TABLE author_list (recipe_id int(11) NOT NULL,author_id int(11) NOT NULL);";
+
+ else if ( tableName == "db_info" ) {
+ commands << "CREATE TABLE db_info (ver FLOAT NOT NULL,generated_by varchar(200) default NULL);";
+ commands << QString( "INSERT INTO db_info VALUES(%1,'Krecipes %2');" ).arg( latestDBVersion() ).arg( krecipes_version() );
+ }
+ else if ( tableName == "ingredient_groups" ) {
+ commands << QString( "CREATE TABLE `ingredient_groups` (`id` int(11) NOT NULL auto_increment, `name` varchar(%1), PRIMARY KEY (`id`));" ).arg( maxIngGroupNameLength() );
+ }
+ else if ( tableName == "yield_types" ) {
+ commands << QString( "CREATE TABLE `yield_types` (`id` int(11) NOT NULL auto_increment, `name` varchar(%1), PRIMARY KEY (`id`));" ).arg( 20 );
+ }
+
+ else if ( tableName == "ratings" )
+ commands << "CREATE TABLE ratings (id INTEGER NOT NULL AUTO_INCREMENT, recipe_id int(11) NOT NULL, comment TEXT, rater TEXT, created TIMESTAMP, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criteria" )
+ commands << "CREATE TABLE rating_criteria (id INTEGER NOT NULL AUTO_INCREMENT, name TEXT, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criterion_list" )
+ commands << "CREATE TABLE rating_criterion_list (rating_id INTEGER NOT NULL, rating_criterion_id INTEGER, stars FLOAT);";
+
+ else
+ return ;
+
+ QSqlQuery databaseToCreate( QString::null, database );
+
+ // execute the queries
+ for ( QStringList::const_iterator it = commands.begin(); it != commands.end(); ++it )
+ databaseToCreate.exec( ( *it ) );
+}
+
+void MySQLRecipeDB::portOldDatabases( float version )
+{
+ kdDebug() << "Current database version is..." << version << "\n";
+ QString command;
+
+ // Note that version no. means the version in which this DB structure
+ // was introduced. To work with SVN users, the database will be incrementally
+ // upgraded for each change made between releases (e.g. 0.81, 0.82,... are
+ // what will become 0.9)
+
+ if ( qRound(version*10) < 3 ) // The database was generated with a version older than v 0.3. First update to 0.3 version
+ {
+
+ // Add new columns to existing tables (creating new tables is not necessary. Integrity check does that before)
+ command = "ALTER TABLE recipes ADD COLUMN persons int(11) AFTER title;";
+ QSqlQuery tableToAlter( command, database );
+
+ // Set the version to the new one (0.3)
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.3,'Krecipes 0.4');"; // Set the new version
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 4 ) // Upgrade to the current DB version 0.4
+ {
+
+ // Add new columns to existing tables (creating any new tables is not necessary. Integrity check does that before)
+ command = "ALTER TABLE ingredient_list ADD COLUMN order_index int(11) AFTER unit_id;";
+ QSqlQuery tableToAlter( command, database );
+
+ // Missing indexes in the previous versions
+ command = "CREATE index rid_index ON category_list(recipe_id)";
+ tableToAlter.exec( command );
+
+ command = "CREATE index cid_index ON category_list(category_id)";
+ tableToAlter.exec( command );
+
+ command = "CREATE index ridil_index ON ingredient_list(recipe_id)";
+ tableToAlter.exec( command );
+
+ command = "CREATE index iidil_index ON ingredient_list(ingredient_id)";
+ tableToAlter.exec( command );
+
+ // Port data
+
+ //*1:: Recipes have always category -1 to speed up searches (no JOINs needed)
+ command = "SELECT r.id FROM recipes r;"; // Find all recipes
+ QSqlQuery categoryToAdd( QString::null, database );
+ tableToAlter.exec( command );
+ if ( tableToAlter.isActive() )
+ {
+ while ( tableToAlter.next() ) {
+ int recipeId = tableToAlter.value( 0 ).toInt();
+ QString cCommand = QString( "INSERT INTO category_list VALUES (%1,-1);" ).arg( recipeId );
+ categoryToAdd.exec( cCommand );
+
+ emit progress();
+ }
+ }
+
+ // Set the version to the new one (0.4)
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.4,'Krecipes 0.4');"; // Set the new version
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 5 ) {
+ command = QString( "CREATE TABLE prep_methods (id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxPrepMethodNameLength() );
+ QSqlQuery tableToAlter( command, database );
+
+ command = "ALTER TABLE ingredient_list ADD COLUMN prep_method_id int(11) AFTER unit_id;";
+ tableToAlter.exec( command );
+ command = "UPDATE ingredient_list SET prep_method_id=-1 WHERE prep_method_id IS NULL;";
+ tableToAlter.exec( command );
+
+ command = "ALTER TABLE authors MODIFY name VARCHAR(50);";
+ tableToAlter.exec( command );
+ command = "ALTER TABLE categories MODIFY name VARCHAR(40);";
+ tableToAlter.exec( command );
+
+ // Set the version to the new one (0.5)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.5,'Krecipes 0.5');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 6 ) {
+ command = "ALTER TABLE categories ADD COLUMN parent_id int(11) NOT NULL default '-1' AFTER name;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.6,'Krecipes 0.6');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 61 ) {
+ QString command = "ALTER TABLE `recipes` ADD COLUMN `prep_time` TIME DEFAULT NULL";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.61,'Krecipes 0.6');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 62 ) {
+ QString command = "ALTER TABLE `ingredient_list` ADD COLUMN `group_id` int(11) default '-1' AFTER order_index;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.62,'Krecipes 0.7');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 63 ) {
+ QString command = "ALTER TABLE `units` ADD COLUMN `plural` varchar(20) DEFAULT NULL AFTER name;";
+ QSqlQuery tableToAlter( command, database );
+
+ QSqlQuery result( "SELECT id,name FROM units WHERE plural IS NULL", database );
+ if ( result.isActive() ) {
+ while ( result.next() ) {
+ command = "UPDATE units SET plural='" + result.value( 1 ).toString() + "' WHERE id=" + QString::number( result.value( 0 ).toInt() );
+ QSqlQuery query( command, database );
+
+ emit progress();
+ }
+ }
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ tableToAlter.exec( command );
+ command = "INSERT INTO db_info VALUES(0.63,'Krecipes 0.7');";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*10) < 7 ) { //simply call 0.63 -> 0.7
+ QString command = "UPDATE db_info SET ver='0.7';";
+ QSqlQuery query( command, database );
+ }
+
+ if ( qRound(version*100) < 81 ) {
+ QString command = "ALTER TABLE `ingredient_list` ADD COLUMN `amount_offset` FLOAT DEFAULT '0' AFTER amount;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "UPDATE db_info SET ver='0.81',generated_by='Krecipes SVN (20050816)';";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 82 ) {
+ QString command = "ALTER TABLE `recipes` ADD COLUMN `yield_amount` FLOAT DEFAULT '0' AFTER persons;";
+ QSqlQuery tableToAlter( command, database );
+
+ command = "ALTER TABLE `recipes` ADD COLUMN `yield_amount_offset` FLOAT DEFAULT '0' AFTER yield_amount;";
+ tableToAlter.exec(command);
+
+ command = "ALTER TABLE `recipes` ADD COLUMN `yield_type_id` INTEGER DEFAULT '-1' AFTER yield_amount_offset;";
+ tableToAlter.exec(command);
+
+ QSqlQuery result( "SELECT id,persons FROM recipes", database );
+ if ( result.isActive() ) {
+ while ( result.next() ) {
+ command = "UPDATE recipes SET yield_amount='" + QString::number( result.value( 1 ).toInt() ) + "' WHERE id=" + QString::number( result.value( 0 ).toInt() );
+ QSqlQuery query( command, database );
+
+ emit progress();
+ }
+ }
+
+ command = "ALTER TABLE `recipes` DROP COLUMN `persons`;";
+ tableToAlter.exec( command );
+
+ command = "UPDATE db_info SET ver='0.82',generated_by='Krecipes SVN (20050902)';";
+ tableToAlter.exec( command );
+ }
+
+ if ( qRound(version*100) < 83 ) {
+ database->transaction();
+
+ //====add a id columns to 'ingredient_list' to identify it for the prep method list
+ database->exec( "RENAME TABLE ingredient_list TO ingredient_list_copy;" );
+ database->exec( "CREATE TABLE ingredient_list (id INTEGER NOT NULL AUTO_INCREMENT, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, PRIMARY KEY(id), INDEX ridil_index(recipe_id), INDEX iidil_index(ingredient_id));" );
+
+ QSqlQuery copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO ingredient_list VALUES (NULL, ?, ?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( copyQuery.value( 3 ) );
+ query.addBindValue( copyQuery.value( 4 ) );
+ query.addBindValue( copyQuery.value( 6 ) );
+ query.addBindValue( copyQuery.value( 7 ) );
+ query.exec();
+
+ int prep_method_id = copyQuery.value( 5 ).toInt();
+ if ( prep_method_id != -1 ) {
+ query.prepare( "INSERT INTO prep_method_list VALUES (?, ?, ?);" );
+ query.addBindValue( lastInsertID() );
+ query.addBindValue( prep_method_id );
+ query.addBindValue( 1 );
+ query.exec();
+ }
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.83',generated_by='Krecipes SVN (20050909)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.83 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 84 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE recipes ADD COLUMN ctime TIMESTAMP;" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN mtime TIMESTAMP;" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN atime TIMESTAMP;" );
+
+ database->exec( "UPDATE recipes SET ctime=CURRENT_TIMESTAMP, mtime=CURRENT_TIMESTAMP, atime=CURRENT_TIMESTAMP;" );
+
+ database->exec( "UPDATE db_info SET ver='0.84',generated_by='Krecipes SVN (20050913)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.84 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 85 ) {
+ database->transaction();
+
+ QSqlQuery query( "SELECT id,photo FROM recipes", database );
+
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ storePhoto( query.value(0).toInt(), query.value(1).toByteArray() );
+
+ emit progress();
+ }
+ }
+
+
+ database->exec( "UPDATE db_info SET ver='0.85',generated_by='Krecipes SVN (20050926)';" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.85 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 86 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE ingredient_list ADD INDEX (group_id)" );
+
+ QSqlQuery query( "SELECT id,name FROM ingredient_groups ORDER BY name", database );
+
+ QString last;
+ int lastID;
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QString name = query.value(1).toString();
+ int id = query.value(0).toInt();
+ if ( last == name ) {
+ QString command = QString("UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2").arg(lastID).arg(id);
+ database->exec(command);
+
+ command = QString("DELETE FROM ingredient_groups WHERE id=%1").arg(id);
+ database->exec(command);
+ }
+ last = name;
+ lastID = id;
+
+ emit progress();
+ }
+ }
+
+ database->exec( "UPDATE db_info SET ver='0.86',generated_by='Krecipes SVN (20050928)';" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.86 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 87 ) {
+ //Load this default data so the user knows what rating criteria is
+ database->exec( QString("INSERT INTO rating_criteria VALUES (1,'%1')").arg(i18n("Overall")) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (2,'%1')").arg(i18n("Taste") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (3,'%1')").arg(i18n("Appearance") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (4,'%1')").arg(i18n("Originality") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (5,'%1')").arg(i18n("Ease of Preparation") ) );
+
+ database->exec( "UPDATE db_info SET ver='0.87',generated_by='Krecipes SVN (20051014)'" );
+ }
+
+ if ( qRound(version*100) < 90 ) {
+ database->exec("UPDATE db_info SET ver='0.9',generated_by='Krecipes 0.9'");
+ }
+
+ if ( qRound(version*100) < 91 ) {
+ database->exec("CREATE index parent_id_index ON categories(parent_id)");
+ database->exec("UPDATE db_info SET ver='0.91',generated_by='Krecipes SVN (20060526)'");
+ }
+
+ if ( qRound(version*100) < 92 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE units ADD COLUMN name_abbrev VARCHAR(20) AFTER name");
+ database->exec( "ALTER TABLE units ADD COLUMN plural_abbrev VARCHAR(20) AFTER plural");
+
+ database->exec("UPDATE db_info SET ver='0.92',generated_by='Krecipes SVN (20060609)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.92 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 93 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE ingredient_list ADD COLUMN substitute_for INTEGER AFTER group_id");
+
+ database->exec("UPDATE db_info SET ver='0.93',generated_by='Krecipes SVN (20060615)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.93 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 94 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE units ADD COLUMN type INTEGER NOT NULL DEFAULT 0 AFTER plural_abbrev");
+
+ database->exec("UPDATE db_info SET ver='0.94',generated_by='Krecipes SVN (20060712)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.94 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 95 ) {
+ database->exec( "DROP TABLE ingredient_weights" );
+ createTable( "ingredient_weights" );
+ database->exec( "UPDATE db_info SET ver='0.95',generated_by='Krecipes SVN (20060726)'" );
+ }
+}
+
+int MySQLRecipeDB::lastInsertID()
+{
+ QSqlQuery lastInsertID( "SELECT LAST_INSERT_ID();", database );
+
+ int id = -1;
+ if ( lastInsertID.isActive() && lastInsertID.next() )
+ id = lastInsertID.value( 0 ).toInt();
+
+ return id;
+}
+
+void MySQLRecipeDB::givePermissions( const QString &dbName, const QString &username, const QString &password, const QString &clientHost )
+{
+ QString command;
+
+ if ( !password.isEmpty() )
+ command = QString( "GRANT ALL ON %1.* TO '%2'@'%3' IDENTIFIED BY '%4';" ).arg( dbName ).arg( username ).arg( clientHost ).arg( password );
+ else
+ command = QString( "GRANT ALL ON %1.* TO '%2'@'%3';" ).arg( dbName ).arg( username ).arg( clientHost );
+
+ kdDebug() << "I'm doing the query to setup permissions\n";
+
+ QSqlQuery permissionsToSet( command, database );
+}
+
+#include "mysqlrecipedb.moc"
diff --git a/krecipes/src/backends/MySQL/mysqlrecipedb.h b/krecipes/src/backends/MySQL/mysqlrecipedb.h
new file mode 100644
index 0000000..01ac5a4
--- /dev/null
+++ b/krecipes/src/backends/MySQL/mysqlrecipedb.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef MYSQLRECIPEDB_H
+#define MYSQLRECIPEDB_H
+
+#define MYSQL_DRIVER "QMYSQL3"
+
+#include "qsqlrecipedb.h"
+
+/**
+@author Unai Garro
+*/
+class MySQLRecipeDB : public QSqlRecipeDB
+{
+
+ Q_OBJECT
+
+private:
+ void createDB( void );
+
+public:
+ MySQLRecipeDB( const QString &host, const QString &user = QString::null, const QString &pass = QString::null, const QString &DBName = DEFAULT_DB_NAME, int port = 0 );
+ ~MySQLRecipeDB( void );
+
+ int lastInsertID();
+
+ void createTable( const QString &tableName );
+ void givePermissions( const QString &dbName, const QString &username, const QString &password = QString::null, const QString &clientHost = "localhost" );
+
+protected:
+ QString qsqlDriverPlugin() const
+ {
+ return MYSQL_DRIVER;
+ }
+
+private:
+ void portOldDatabases( float version );
+ QStringList backupCommand() const;
+ QStringList restoreCommand() const;
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/PostgreSQL/Makefile.am b/krecipes/src/backends/PostgreSQL/Makefile.am
new file mode 100644
index 0000000..9295a6f
--- /dev/null
+++ b/krecipes/src/backends/PostgreSQL/Makefile.am
@@ -0,0 +1,17 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../.. $(all_includes)
+
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecpsql.la
+libkrecpsql_la_SOURCES=psqlrecipedb.cpp
+libkrecpsql_la_METASOURCES=AUTO
+
+
+#the library search path.
+libkrecpsql_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp b/krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp
new file mode 100644
index 0000000..c3a94e4
--- /dev/null
+++ b/krecipes/src/backends/PostgreSQL/psqlrecipedb.cpp
@@ -0,0 +1,560 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "psqlrecipedb.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kconfig.h>
+
+#include <qvariant.h>
+
+//Note: PostgreSQL's database names are always lowercase
+PSqlRecipeDB::PSqlRecipeDB( const QString& host, const QString& user, const QString& pass, const QString& DBname, int port ) : QSqlRecipeDB( host, user, pass, DBname.lower(), port )
+{}
+
+PSqlRecipeDB::~PSqlRecipeDB()
+{}
+
+void PSqlRecipeDB::createDB()
+{
+ QString real_db_name = database->databaseName();
+
+ //we have to be connected to some database in order to create the Krecipes database
+ //so long as the permissions given are allowed access to "template1', this works
+ database->setDatabaseName( "template1" );
+ if ( database->open() ) {
+ QSqlQuery query( QString( "CREATE DATABASE %1" ).arg( real_db_name ), database );
+ if ( !query.isActive() )
+ kdDebug() << "create query failed: " << database->lastError().databaseText() << endl;
+
+ database->close();
+ }
+ else
+ kdDebug() << "create open failed: " << database->lastError().databaseText() << endl;
+
+ database->setDatabaseName( real_db_name );
+}
+
+QStringList PSqlRecipeDB::backupCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "PgDumpPath", "pg_dump" )<<"-d"<<database->databaseName()
+ <<"-U"<<config->readEntry( "Username" );
+
+ int port = config->readNumEntry( "Port", 0 );
+ if ( port > 0 )
+ command<<"-p"<<QString::number(port);
+
+ return command;
+}
+
+QStringList PSqlRecipeDB::restoreCommand() const
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+
+ QStringList command;
+ command<<config->readEntry( "PsqlPath", "psql" )<<database->databaseName()
+ <<"-U"<<config->readEntry( "Username" );
+
+ int port = config->readNumEntry( "Port", 0 );
+ if ( port > 0 )
+ command<<"-p"<<QString::number(port);
+
+ return command;
+}
+
+void PSqlRecipeDB::createTable( const QString &tableName )
+{
+
+ QStringList commands;
+
+ if ( tableName == "recipes" )
+ commands << "CREATE TABLE recipes (id SERIAL NOT NULL PRIMARY KEY,title CHARACTER VARYING, yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo TEXT, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP );";
+
+ else if ( tableName == "ingredients" )
+ commands << "CREATE TABLE ingredients (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+
+ else if ( tableName == "ingredient_list" ) {
+ commands << "CREATE TABLE ingredient_list (id SERIAL NOT NULL PRIMARY KEY, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, substitute_for INTEGER);";
+ commands << "CREATE INDEX ridil_index ON ingredient_list USING BTREE (recipe_id);";
+ commands << "CREATE INDEX iidil_index ON ingredient_list USING BTREE (ingredient_id);";
+ commands << "CREATE INDEX gidil_index ON ingredient_list USING BTREE (group_id);";
+ }
+
+ else if ( tableName == "unit_list" )
+ commands << "CREATE TABLE unit_list (ingredient_id INTEGER, unit_id INTEGER);";
+
+ else if ( tableName == "units" )
+ commands << "CREATE TABLE units (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING, name_abbrev CHARACTER VARYING, plural CHARACTER VARYING, plural_abbrev CHARACTER VARYING, type INTEGER NOT NULL DEFAULT '0' );";
+
+ else if ( tableName == "prep_methods" )
+ commands << "CREATE TABLE prep_methods (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+ else if ( tableName == "prep_method_list" ) {
+ commands << "CREATE TABLE prep_method_list (ingredient_list_id INTEGER NOT NULL,prep_method_id INTEGER NOT NULL, order_index INTEGER);";
+ commands << "CREATE INDEX iid_index ON prep_method_list USING BTREE (ingredient_list_id);";
+ commands << "CREATE INDEX pid_index ON prep_method_list USING BTREE (prep_method_id);";
+ }
+ else if ( tableName == "ingredient_info" )
+ commands << "CREATE TABLE ingredient_info (ingredient_id INTEGER, property_id INTEGER, amount FLOAT, per_units INTEGER);";
+
+ else if ( tableName == "ingredient_properties" )
+ commands << "CREATE TABLE ingredient_properties (id SERIAL NOT NULL,name CHARACTER VARYING, units CHARACTER VARYING);";
+
+ else if ( tableName == "ingredient_weights" ) {
+ commands << "CREATE TABLE ingredient_weights (id SERIAL NOT NULL PRIMARY KEY, ingredient_id INTEGER NOT NULL, amount FLOAT, unit_id INTEGER, weight FLOAT, weight_unit_id INTEGER, prep_method_id INTEGER );"
+ << "CREATE INDEX weight_wid_index ON ingredient_weights USING BTREE (weight_unit_id)"
+ << "CREATE INDEX weight_pid_index ON ingredient_weights USING BTREE (prep_method_id)"
+ << "CREATE INDEX weight_uid_index ON ingredient_weights USING BTREE (unit_id)"
+ << "CREATE INDEX weight_iid_index ON ingredient_weights USING BTREE (ingredient_id)";
+ }
+
+ else if ( tableName == "units_conversion" )
+ commands << "CREATE TABLE units_conversion (unit1_id INTEGER, unit2_id INTEGER, ratio FLOAT);";
+
+ else if ( tableName == "categories" ) {
+ commands << "CREATE TABLE categories (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING default NULL, parent_id INTEGER NOT NULL default -1);";
+ commands << "CREATE index parent_id_index ON categories USING BTREE(parent_id);";
+ }
+ else if ( tableName == "category_list" ) {
+ commands << "CREATE TABLE category_list (recipe_id INTEGER NOT NULL,category_id INTEGER NOT NULL);";
+ commands << "CREATE INDEX rid_index ON category_list USING BTREE (recipe_id);";
+ commands << "CREATE INDEX cid_index ON category_list USING BTREE (category_id);";
+ }
+
+ else if ( tableName == "authors" )
+ commands << "CREATE TABLE authors (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING default NULL);";
+
+ else if ( tableName == "author_list" )
+ commands << "CREATE TABLE author_list (recipe_id INTEGER NOT NULL,author_id INTEGER NOT NULL);";
+
+ else if ( tableName == "db_info" ) {
+ commands << "CREATE TABLE db_info (ver FLOAT NOT NULL,generated_by CHARACTER VARYING default NULL);";
+ commands << QString( "INSERT INTO db_info VALUES(%1,'Krecipes %2');" ).arg( latestDBVersion() ).arg( krecipes_version() );
+ }
+ else if ( tableName == "ingredient_groups" ) {
+ commands << "CREATE TABLE ingredient_groups (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+ }
+ else if ( tableName == "yield_types" ) {
+ commands << "CREATE TABLE yield_types (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+ }
+
+ else if ( tableName == "ratings" )
+ commands << "CREATE TABLE ratings (id SERIAL NOT NULL PRIMARY KEY, recipe_id INTEGER NOT NULL, comment CHARACTER VARYING, rater CHARACTER VARYING, created TIMESTAMP);";
+
+ else if ( tableName == "rating_criteria" )
+ commands << "CREATE TABLE rating_criteria (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING);";
+
+ else if ( tableName == "rating_criterion_list" )
+ commands << "CREATE TABLE rating_criterion_list (rating_id INTEGER NOT NULL, rating_criterion_id INTEGER, stars FLOAT);";
+ else
+ return ;
+
+ QSqlQuery databaseToCreate( QString::null, database );
+
+ // execute the queries
+ for ( QStringList::const_iterator it = commands.begin(); it != commands.end(); ++it )
+ databaseToCreate.exec( *it );
+}
+
+void PSqlRecipeDB::initializeData()
+{
+ QSqlRecipeDB::initializeData();
+
+ QSqlQuery updateSeq( "SELECT setval('units_id_seq',(SELECT COUNT(1) FROM units))", database );
+ updateSeq.exec( "SELECT setval('categories_id_seq',(SELECT COUNT(1) FROM categories))" );
+}
+
+void PSqlRecipeDB::portOldDatabases( float version )
+{
+ kdDebug() << "Current database version is..." << version << "\n";
+ QString command;
+
+ if ( qRound(version*10) < 7 ) {
+ //version added
+ }
+
+ if ( qRound(version*100) < 81 ) {
+ database->transaction();
+
+ addColumn("CREATE TABLE %1 (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, %2 unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER);","amount_offset FLOAT","'0'","ingredient_list",3);
+
+ QSqlQuery query(QString::null,database);
+ query.exec( "CREATE INDEX ridil_index ON ingredient_list USING BTREE (recipe_id);" );
+ query.exec( "CREATE INDEX iidil_index ON ingredient_list USING BTREE (ingredient_id);");
+
+ query.exec( "UPDATE db_info SET ver='0.81',generated_by='Krecipes SVN (20050816)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.81 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 82 ) {
+ database->transaction();
+
+ //==================add a columns to 'recipes' to allow yield range + yield type
+ database->exec( "CREATE TABLE recipes_copy (id SERIAL NOT NULL PRIMARY KEY,title CHARACTER VARYING, persons INTEGER, instructions TEXT, photo TEXT, prep_time TIME);" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO recipes_copy VALUES (?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( copyQuery.value( 3 ) );
+ query.addBindValue( copyQuery.value( 4 ) );
+ query.addBindValue( copyQuery.value( 5 ) );
+ query.exec();
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id SERIAL NOT NULL PRIMARY KEY,title CHARACTER VARYING, yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo TEXT, prep_time TIME);" );
+ copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO recipes VALUES (?, ?, ?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) ); //id
+ query.addBindValue( copyQuery.value( 1 ) ); //title
+ query.addBindValue( copyQuery.value( 2 ) ); //persons, now yield_amount
+ query.addBindValue( 0 ); //yield_amount_offset
+ query.addBindValue( -1 ); //yield_type_id
+ query.addBindValue( copyQuery.value( 3 ) ); //instructions
+ query.addBindValue( copyQuery.value( 4 ) ); //photo
+ query.addBindValue( copyQuery.value( 5 ) ); //prep_time
+ query.exec();
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.82',generated_by='Krecipes SVN (20050902)';" );
+
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.82 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 83 ) {
+ database->transaction();
+
+ //====add a id columns to 'ingredient_list' to identify it for the prep method list
+ database->exec( "ALTER TABLE ingredient_list RENAME TO ingredient_list_copy;" );
+
+ database->exec( "CREATE TABLE ingredient_list (id SERIAL NOT NULL PRIMARY KEY, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER);" );
+
+ QSqlQuery copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ int ing_list_id = getNextInsertID("ingredient_list","id");
+
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO ingredient_list VALUES (?, ?, ?, ?, ?, ?, ?, ?)" );
+ query.addBindValue( ing_list_id );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( copyQuery.value( 3 ) );
+ query.addBindValue( copyQuery.value( 4 ) );
+ query.addBindValue( copyQuery.value( 6 ) );
+ query.addBindValue( copyQuery.value( 7 ) );
+ query.exec();
+
+ int prep_method_id = copyQuery.value( 5 ).toInt();
+ if ( prep_method_id != -1 ) {
+ query.prepare( "INSERT INTO prep_method_list VALUES (?, ?, ?);" );
+ query.addBindValue( ing_list_id );
+ query.addBindValue( prep_method_id );
+ query.addBindValue( 1 );
+ query.exec();
+ }
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "CREATE INDEX ridil_index ON ingredient_list USING BTREE (recipe_id);" );
+ database->exec( "CREATE INDEX iidil_index ON ingredient_list USING BTREE (ingredient_id);" );
+
+ database->exec( "UPDATE db_info SET ver='0.83',generated_by='Krecipes SVN (20050909)';" );
+
+ if ( !database->commit() ) {
+ kdDebug()<<"Update to 0.83 failed. Maybe you should try again."<<endl;
+ return;
+ }
+ }
+
+ if ( qRound(version*100) < 84 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE recipes ADD COLUMN ctime TIMESTAMP" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN mtime TIMESTAMP" );
+ database->exec( "ALTER TABLE recipes ADD COLUMN atime TIMESTAMP" );
+
+ database->exec( "UPDATE recipes SET ctime=CURRENT_TIMESTAMP, mtime=CURRENT_TIMESTAMP, atime=CURRENT_TIMESTAMP;" );
+
+ database->exec( "UPDATE db_info SET ver='0.84',generated_by='Krecipes SVN (20050913)';" );
+
+ if ( !database->commit() ) {
+ kdDebug()<<"Update to 0.84 failed. Maybe you should try again."<<endl;
+ return;
+ }
+ }
+
+ if ( qRound(version*100) < 85 ) { //this change altered the photo format, but this backend already used the newer format
+ database->transaction();
+
+ database->exec( "UPDATE db_info SET ver='0.85',generated_by='Krecipes SVN (20050926)';" );
+
+ if ( !database->commit() ) {
+ kdDebug()<<"Update to 0.85 failed. Maybe you should try again."<<endl;
+ return;
+ }
+ }
+
+ if ( qRound(version*100) < 86 ) {
+ database->transaction();
+
+ database->exec( "CREATE INDEX gidil_index ON ingredient_list USING BTREE (group_id);" );
+
+ QSqlQuery query( "SELECT id,name FROM ingredient_groups ORDER BY name", database );
+
+ QString last;
+ int lastID;
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QString name = query.value(1).toString();
+ int id = query.value(0).toInt();
+ if ( last == name ) {
+ QString command = QString("UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2").arg(lastID).arg(id);
+ database->exec(command);
+
+ command = QString("DELETE FROM ingredient_groups WHERE id=%1").arg(id);
+ database->exec(command);
+ }
+ last = name;
+ lastID = id;
+
+ emit progress();
+ }
+ }
+
+ database->exec( "UPDATE db_info SET ver='0.86',generated_by='Krecipes SVN (20050928)';" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.86 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 87 ) {
+ //Load this default data so the user knows what rating criteria is
+ database->exec( QString("INSERT INTO rating_criteria VALUES (1,'%1')").arg(i18n("Overall")) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (2,'%1')").arg(i18n("Taste") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (3,'%1')").arg(i18n("Appearance") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (4,'%1')").arg(i18n("Originality") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (5,'%1')").arg(i18n("Ease of Preparation") ) );
+
+ database->exec( "UPDATE db_info SET ver='0.87',generated_by='Krecipes SVN (20051014)'" );
+ }
+
+ if ( qRound(version*100) < 90 ) {
+ database->exec("UPDATE db_info SET ver='0.9',generated_by='Krecipes 0.9'");
+ }
+
+ if ( qRound(version*100) < 91 ) {
+ database->exec("CREATE index parent_id_index ON categories USING BTREE(parent_id)");
+ database->exec("UPDATE db_info SET ver='0.91',generated_by='Krecipes SVN (20060526)'");
+ }
+
+ if ( qRound(version*100) < 92 ) {
+ database->transaction();
+
+ //==================add a columns to 'units' to allow unit abbreviations
+ database->exec( "ALTER TABLE units RENAME TO units_copy" );
+
+ int nextval = -1;
+ QSqlQuery getID( "SELECT nextval('units_id_seq')", database );
+ if ( getID.isActive() && getID.first() )
+ nextval = getID.value( 0 ).toInt();
+ if ( nextval == -1 )
+ kdDebug() << "Database update failed! Unable to update units sequence." << endl;
+
+ database->exec( "CREATE TABLE units (id SERIAL NOT NULL PRIMARY KEY, name CHARACTER VARYING, name_abbrev CHARACTER VARYING, plural CHARACTER VARYING, plural_abbrev CHARACTER VARYING )" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name,plural FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QSqlQuery query(QString::null,database);
+ query.prepare( "INSERT INTO units VALUES(?, ?, ?, ?, ?)" );
+ query.addBindValue( copyQuery.value( 0 ) );
+ query.addBindValue( copyQuery.value( 1 ) );
+ query.addBindValue( QVariant() );
+ query.addBindValue( copyQuery.value( 2 ) );
+ query.addBindValue( QVariant() );
+ query.exec();
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+ database->exec( "ALTER TABLE units_id_seq1 RENAME TO units_id_seq" );
+ database->exec( "ALTER SEQUENCE units_id_seq RESTART WITH "+QString::number(nextval) );
+
+ database->exec("UPDATE db_info SET ver='0.92',generated_by='Krecipes SVN (20060609)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.92 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 93 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE ingredient_list ADD COLUMN substitute_for INTEGER" );
+
+ database->exec("UPDATE db_info SET ver='0.93',generated_by='Krecipes SVN (20060616)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.93 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 94 ) {
+ database->transaction();
+
+ database->exec( "ALTER TABLE units ADD COLUMN type INTEGER NOT NULL DEFAULT '0'" );
+
+ database->exec("UPDATE db_info SET ver='0.94',generated_by='Krecipes SVN (20060712)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.94 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 95 ) {
+ database->exec( "DROP TABLE ingredient_weights" );
+ createTable( "ingredient_weights" );
+ database->exec( "UPDATE db_info SET ver='0.95',generated_by='Krecipes SVN (20060726)'" );
+ }
+}
+
+void PSqlRecipeDB::addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index )
+{
+ QString command;
+
+ command = QString(new_table_sql).arg(table_name+"_copy").arg(QString::null);
+ kdDebug()<<"calling: "<<command<<endl;
+ QSqlQuery query( command, database );
+
+ command = "SELECT * FROM "+table_name+";";
+ query.exec( command );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ QVariant variant = query.value(i);
+ if ( variant.type() == QVariant::Invalid ) break;
+
+ dataList << "'"+variant.toString()+"'";
+ }
+ command = "INSERT INTO "+table_name+"_copy VALUES("+dataList.join(",")+");";
+ kdDebug()<<"calling: "<<command<<endl;
+ QSqlQuery insert_query( command, database );
+
+ emit progress();
+ }
+ }
+ query.exec( "DROP TABLE "+table_name+";" );
+ query.exec( QString(new_table_sql).arg(table_name).arg(new_col_info+",") );
+ query.exec( "SELECT * FROM "+table_name+"_copy;" );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ if ( i == col_index )
+ dataList << default_value;
+
+ QVariant variant = query.value(i);
+ if ( variant.type() == QVariant::Invalid ) break;
+
+ dataList << "'"+variant.toString()+"'";
+ }
+ command = "INSERT INTO "+table_name+" VALUES(" +dataList.join(",")+");";
+ QSqlQuery insert_query( command, database );
+ kdDebug()<<"calling: "<<command<<endl;
+
+ emit progress();
+ }
+ }
+ query.exec( "DROP TABLE "+table_name+"_copy;" );
+}
+
+int PSqlRecipeDB::lastInsertID()
+{
+ return last_insert_id;
+}
+
+int PSqlRecipeDB::getNextInsertID( const QString &table, const QString &column )
+{
+ QString command = QString( "SELECT nextval('%1_%2_seq');" ).arg( table ).arg( column );
+ QSqlQuery getID( command, database );
+
+ if ( getID.isActive() && getID.first() ) {
+ last_insert_id = getID.value( 0 ).toInt();
+ }
+ else
+ last_insert_id = -1;
+
+ return last_insert_id;
+}
+
+void PSqlRecipeDB::givePermissions( const QString & /*dbName*/, const QString &username, const QString &password, const QString & /*clientHost*/ )
+{
+ QStringList tables;
+ tables << "ingredient_info" << "ingredient_list" << "ingredient_properties" << "ingredients" << "recipes" << "unit_list" << "units" << "units_conversion" << "categories" << "category_list" << "authors" << "author_list" << "prep_methods" << "db_info" << "ingredient_groups" << "ingredient_weights" << "prep_method_list" << "yield_types" << "ratings" << "rating_criteria" << "rating_criterion_list";
+
+ //we also have to grant permissions on the sequences created
+ tables << "authors_id_seq" << "categories_id_seq" << "ingredient_properties_id_seq" << "ingredient_weights_id_seq" << "ingredients_id_seq" << "prep_methods_id_seq" << "recipes_id_seq" << "units_id_seq" << "ingredient_groups_id_seq" << "yield_types_id_seq" << "ingredient_list_id_seq" << "ratings_id_seq" << "rating_criteria_id_seq";
+
+ QString command;
+
+ kdDebug() << "I'm doing the query to create the new user" << endl;
+ command = "CREATE USER " + username;
+ if ( !password.isEmpty() )
+ command.append( "WITH PASSWORD '" + password + "'" );
+ command.append( ";" );
+ QSqlQuery permissionsToSet( command, database );
+
+ kdDebug() << "I'm doing the query to setup permissions\n";
+ command = QString( "GRANT ALL ON %1 TO %2;" ).arg( tables.join( "," ) ).arg( username );
+ permissionsToSet.exec( command );
+}
+
+void PSqlRecipeDB::empty( void )
+{
+ QSqlRecipeDB::empty();
+
+ QStringList tables;
+ tables << "authors_id_seq" << "categories_id_seq" << "ingredient_properties_id_seq" << "ingredient_weights_id_seq" << "ingredients_id_seq" << "prep_methods_id_seq" << "recipes_id_seq" << "units_id_seq" << "ingredient_groups_id_seq" << "yield_types_id_seq" << "ingredient_list_id_seq" << "prep_method_list_id_seq" << "ratings_id_seq" << "rating_criteria_id_seq";
+
+ QSqlQuery tablesToEmpty( QString::null, database );
+ for ( QStringList::Iterator it = tables.begin(); it != tables.end(); ++it ) {
+ QString command = QString( "DELETE FROM %1;" ).arg( *it );
+ tablesToEmpty.exec( command );
+ }
+}
+
+#include "psqlrecipedb.moc"
diff --git a/krecipes/src/backends/PostgreSQL/psqlrecipedb.h b/krecipes/src/backends/PostgreSQL/psqlrecipedb.h
new file mode 100644
index 0000000..9727909
--- /dev/null
+++ b/krecipes/src/backends/PostgreSQL/psqlrecipedb.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef PSQLRECIPEDB_H
+#define PSQLRECIPEDB_H
+
+#define PSQL_DRIVER "QPSQL7"
+
+#include "qsqlrecipedb.h"
+
+/**
+@author Jason Kivlighn
+*/
+class PSqlRecipeDB : public QSqlRecipeDB
+{
+
+ Q_OBJECT
+
+private:
+ void createDB( void );
+
+public:
+ PSqlRecipeDB( const QString& host, const QString& user = QString::null, const QString& pass = QString::null, const QString& DBName = DEFAULT_DB_NAME, int port = 0 );
+ ~PSqlRecipeDB( void );
+
+ int lastInsertID();
+ void initializeData();
+
+ void createTable( const QString &tableName );
+ void givePermissions( const QString &dbName, const QString &username, const QString &password, const QString &clientHost );
+
+protected:
+ virtual QString qsqlDriverPlugin() const
+ {
+ return PSQL_DRIVER;
+ }
+ virtual int getNextInsertID( const QString &table, const QString &column );
+
+ virtual void empty( void );
+
+private:
+ void portOldDatabases( float version );
+ QStringList backupCommand() const;
+ QStringList restoreCommand() const;
+
+ void addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index );
+
+ int last_insert_id;
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/SQLite/Makefile.am b/krecipes/src/backends/SQLite/Makefile.am
new file mode 100644
index 0000000..c29f5d3
--- /dev/null
+++ b/krecipes/src/backends/SQLite/Makefile.am
@@ -0,0 +1,19 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../.. $(all_includes)
+
+SUBDIRS = libqsqlite
+
+# Instructions for building the convenience library
+noinst_LTLIBRARIES=libkrecsqlite.la
+libkrecsqlite_la_SOURCES=literecipedb.cpp qsql_sqlite.cpp
+libkrecsqlite_la_METASOURCES=AUTO
+
+libkrecsqlite_la_LIBADD=libqsqlite/libkrecqsqlite.la
+
+#the library search path.
+libkrecsqlite_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/backends/SQLite/libqsqlite/Makefile.am b/krecipes/src/backends/SQLite/libqsqlite/Makefile.am
new file mode 100644
index 0000000..b368a5a
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/Makefile.am
@@ -0,0 +1,17 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = $(all_includes)
+
+#building the library
+
+noinst_LTLIBRARIES=libkrecqsqlite.la
+libkrecqsqlite_la_SOURCES=krecqsqlitedb.cpp krecqsqliteresult.cpp
+libkrecqsqlite_la_METASOURCES=AUTO
+
+#the library search path.
+libkrecqsqlite_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp
new file mode 100644
index 0000000..e6175d3
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.cpp
@@ -0,0 +1,171 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#include "krecqsqlitedb.h"
+#include "krecqsqliteresult.h"
+
+#include <qvaluelist.h>
+
+#include <kdebug.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SQLITE
+#include <sqlite.h>
+#elif HAVE_SQLITE3
+#include <sqlite3.h>
+#endif
+#include <stdlib.h>
+
+QSQLiteDB::QSQLiteDB( QObject *, const char * )
+{}
+
+bool QSQLiteDB::open( const QString &dbname )
+{
+ char * errmsg = 0;
+
+#if HAVE_SQLITE
+
+ m_db = sqlite_open( dbname.latin1(), 0, &errmsg );
+#elif HAVE_SQLITE3
+
+ int res = sqlite3_open( dbname.latin1(), &m_db );
+
+ if ( res != SQLITE_OK )
+ return false;
+#endif
+
+ if ( m_db == 0L ) {
+#if HAVE_SQLITE
+ sqlite_freemem( errmsg );
+#elif HAVE_SQLITE3
+ sqlite3_free( errmsg );
+#endif
+ return false;
+ }
+
+ int func_res;
+ #if HAVE_SQLITE
+ func_res = sqlite_create_function(m_db,"lastInsertID",0,&lastInsertID,m_db );
+ #elif HAVE_SQLITE3
+ func_res = sqlite3_create_function(m_db,"lastInsertID",0,SQLITE_ANY,m_db,
+ &lastInsertID, 0, 0 );
+ #endif
+
+ return func_res == 0;
+}
+
+void QSQLiteDB::close()
+{
+ if ( m_db ) {
+#if HAVE_SQLITE
+ sqlite_close( m_db );
+#elif HAVE_SQLITE3
+
+ sqlite3_close( m_db );
+#endif
+
+ m_db = 0L;
+ }
+}
+
+QSQLiteResult QSQLiteDB::executeQuery( const QString &query, int *lastID )
+{
+ QSQLiteResult res;
+ if ( !m_db ) {
+ return res;
+ }
+
+ char *errmsg = 0;
+#if HAVE_SQLITE
+
+ if ( sqlite_exec( m_db, query.latin1(), &call_back, &res, &errmsg ) > 0 )
+#elif HAVE_SQLITE3
+
+ if ( sqlite3_exec( m_db, query.latin1(), &call_back, &res, &errmsg ) > 0 )
+#endif
+
+ {
+ kdDebug() << "SQLite error: " << errmsg << endl <<
+ "\t (Query: " << query << ")" << endl;
+ res.setError( errmsg );
+ res.setStatus( QSQLiteResult::Failure );
+#if HAVE_SQLITE
+ sqlite_freemem( errmsg );
+#elif HAVE_SQLITE3
+ sqlite3_free( errmsg );
+#endif
+ }
+
+ if ( lastID ) {
+#if HAVE_SQLITE
+ * lastID = sqlite_last_insert_rowid( m_db );
+#elif HAVE_SQLITE3
+
+ *lastID = sqlite3_last_insert_rowid( m_db );
+#endif
+
+ }
+
+ res.setStatus( QSQLiteResult::Success );
+
+ return res;
+}
+
+int QSQLiteDB::call_back( void* result, int argc, char** argv, char** columns )
+{
+ QSQLiteResult * res = ( QSQLiteResult* ) result;
+
+ QMap<QString, QCString> tableString;
+ QMap<int, QCString> tableInt;
+
+ for ( int i = 0; i < argc; i++ ) {
+ tableInt.insert( i, argv[ i ] );
+ tableString.insert( columns[ i ],
+ argv[ i ] );
+ }
+
+ QSQLiteResultRow row( tableString, tableInt );
+ res->addRow( row );
+
+ return 0;
+}
+
+#if HAVE_SQLITE
+void QSQLiteDB::lastInsertID(sqlite_func *context,int argc,const char**)
+{
+ Q_ASSERT( argc==0 );
+
+ void *db = sqlite_user_data(context);
+ sqlite_set_result_int(context, sqlite_last_insert_rowid( (sqlite*)db ) );
+}
+#elif HAVE_SQLITE3
+void QSQLiteDB::lastInsertID( sqlite3_context *context, int argc, sqlite3_value ** )
+{
+ Q_ASSERT( argc==0 );
+
+ void *db = sqlite3_user_data(context);
+ sqlite3_result_int(context, sqlite3_last_insert_rowid( (sqlite3*)db ) );
+}
+#endif
+
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h
new file mode 100644
index 0000000..c0df13b
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqlitedb.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef QSQLITEDB_H
+#define QSQLITEDB_H
+
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <qobject.h>
+
+#include "config.h"
+#if HAVE_SQLITE
+#include <sqlite.h>
+#elif HAVE_SQLITE3
+#include <sqlite3.h>
+#endif
+
+#include "krecqsqliteresult.h"
+
+
+class QSQLiteDB
+{
+public:
+ QSQLiteDB( QObject *parent = 0, const char *name = 0 );
+ bool open( const QString &dbname );
+ void close();
+ QSQLiteResult executeQuery( const QString &query, int *lastID = 0 );
+ int size(); //Returns the number of rows returned
+
+private:
+ static int call_back( void* res, int argc, char** argv, char** columns );
+
+ #if HAVE_SQLITE
+ static void lastInsertID(sqlite_func*,int,const char**);
+ sqlite *m_db;
+ #elif HAVE_SQLITE3
+ static void lastInsertID(sqlite3_context *context, int argc, sqlite3_value **argv);
+ sqlite3 *m_db;
+ #endif
+};
+
+#endif
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp
new file mode 100644
index 0000000..4bebfed
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.cpp
@@ -0,0 +1,197 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "krecqsqliteresult.h"
+
+QSQLiteResultRow::QSQLiteResultRow( const TableString& string, const TableInt& Int )
+ : m_string( string ), m_int( Int )
+{}
+
+QSQLiteResultRow::~QSQLiteResultRow()
+{}
+
+QSQLiteResultRow::QSQLiteResultRow( const QSQLiteResultRow& item )
+{
+ *this = item;
+}
+
+QSQLiteResultRow &QSQLiteResultRow::operator=( const QSQLiteResultRow& other )
+{
+ m_string = other.m_string;
+ m_int = other.m_int;
+ return *this;
+}
+
+QSQLiteResultRow::TableString QSQLiteResultRow::tableString() const
+{
+ return m_string;
+}
+
+QSQLiteResultRow::TableInt QSQLiteResultRow::tableInt() const
+{
+ return m_int;
+}
+
+QCString QSQLiteResultRow::data( const QString& columnName, bool *ok )
+{
+ TableString::Iterator it = m_string.find( columnName );
+
+ /* if found */
+ if ( it != m_string.end() ) {
+ if ( ok )
+ * ok = true;
+ return it.data();
+ }
+ else {
+ if ( ok )
+ * ok = false;
+ return QCString(0);
+ }
+
+}
+
+QCString QSQLiteResultRow::data( int column, bool *ok )
+{
+ TableInt::Iterator it = m_int.find( column );
+
+ // if found
+ if ( it != m_int.end() ) {
+ if ( ok )
+ * ok = true;
+ return it.data();
+ }
+ else {
+ if ( ok )
+ * ok = false;
+ return QCString(0);
+ }
+}
+
+/*
+ * DateFormat is 'YYYY-MM-DD'
+ */
+QDate QSQLiteResultRow::dataToDate( const QString& column, bool *ok )
+{
+ QDate date = QDate::currentDate();
+ QString str = data( column, ok );
+ if ( !str.isEmpty() ) {
+ ; // convert
+ }
+ return date;
+}
+
+QDate QSQLiteResultRow::dataToDate( int column, bool *ok )
+{
+ QDate date = QDate::currentDate();
+ QString str = data( column, ok );
+ if ( !str.isEmpty() ) {
+ ; // convert
+ }
+ return date;
+}
+
+QDateTime QSQLiteResultRow::dataToDateTime( const QString& column )
+{
+ QDateTime time = QDateTime::currentDateTime();
+ return time;
+}
+
+QDateTime QSQLiteResultRow::dataToDateTime( int column )
+{
+ QDateTime time = QDateTime::currentDateTime();
+ return time;
+}
+
+QSQLiteResult::QSQLiteResult( enum Status status,
+ const QSQLiteResult::Columns& list,
+ const QString &error )
+ : m_status( status ), m_list( list ), m_error( error )
+{}
+
+QSQLiteResult::~QSQLiteResult()
+{}
+
+QSQLiteResult::Status QSQLiteResult::getStatus() const
+{
+ return m_status;
+}
+
+void QSQLiteResult::setStatus( QSQLiteResult::Status status )
+{
+ m_status = status;
+}
+
+QSQLiteResult::Columns QSQLiteResult::getResults() const
+{
+ return m_list;
+}
+
+void QSQLiteResult::setResults( const QSQLiteResult::Columns& result )
+{
+ m_list = result;
+}
+
+void QSQLiteResult::addRow( QSQLiteResultRow row )
+{
+ m_list.append( row );
+}
+
+QString QSQLiteResult::getError() const
+{
+ return m_error;
+}
+
+void QSQLiteResult::setError( const QString &error )
+{
+ m_error = error;
+}
+
+QSQLiteResultRow QSQLiteResult::first()
+{
+ it = m_list.begin();
+ return ( *it );
+}
+
+QSQLiteResultRow QSQLiteResult::next()
+{
+ ++it;
+ return ( *it );
+}
+
+bool QSQLiteResult::atEnd()
+{
+ if ( it == m_list.end() ) {
+ return true;
+ }
+
+ return false;
+}
+
+QSQLiteResult::Columns::ConstIterator QSQLiteResult::iterator() const
+{
+ QSQLiteResult::Columns::ConstIterator it;
+ it = m_list.begin();
+ return it;
+}
+
+int QSQLiteResult::size() const
+{
+ return ( m_list.size() );
+}
diff --git a/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h
new file mode 100644
index 0000000..d833eda
--- /dev/null
+++ b/krecipes/src/backends/SQLite/libqsqlite/krecqsqliteresult.h
@@ -0,0 +1,142 @@
+/***************************************************************************
+* *
+* Copyright (C) 2003 *
+* by Unai Garro (ugarro@users.sourceforge.net) *
+* Martin Imobersteg <imm@gmx.ch> *
+* and opie project *
+* *
+* *
+* This code was originally developed by the opie project, on which *
+* Martin Imobersteg based his work. *
+* This file is adds a small extension, necessary to perform some minimum *
+* SQL actions *
+* *
+* (this project is different from that in qsqlite.sf.net) *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef QSQLITERESULT_H
+#define QSQLITERESULT_H
+
+#include <qdatetime.h>
+#include <qmap.h>
+#include <qvaluelist.h>
+
+/**
+ * ResultRow represents one row of the resulting answer
+ */
+class QSQLiteResultRow
+{
+public:
+ /**
+ * TableString is used to establish the relations
+ * between the column name and the real item
+ */
+ typedef QMap<QString, QCString> TableString;
+
+ /**
+ * TableInt is used to establish a relation between a
+ * position of a column and the row value
+ */
+ typedef QMap<int, QCString> TableInt;
+
+ /**
+ * Default c'tor. It has a TableString and a TableInt
+ */
+ QSQLiteResultRow( const TableString& = TableString(),
+ const TableInt& = TableInt() );
+ QSQLiteResultRow( const QSQLiteResultRow& );
+ ~QSQLiteResultRow();
+ QSQLiteResultRow &operator=( const QSQLiteResultRow& );
+
+ /**
+ * returns the TableString
+ */
+ TableString tableString() const;
+
+ /**
+ * returns the TableInt
+ */
+ TableInt tableInt() const;
+
+ /**
+ * retrieves the Data from columnName
+ *
+ */
+ QCString data( const QString& columnName, bool *ok = 0 );
+
+ /**
+ * QString for column number
+ */
+ QCString data( int columnNumber, bool *ok = 0 );
+
+ /**
+ * Date conversion from columnName
+ */
+ QDate dataToDate( const QString& columnName, bool *ok = 0 );
+
+ /**
+ * Date conversion from column-number
+ */
+ QDate dataToDate( int columnNumber, bool *ok = 0 );
+
+ QDateTime dataToDateTime( const QString& columName );
+ QDateTime dataToDateTime( int columnNumber );
+
+private:
+ TableString m_string;
+ TableInt m_int;
+};
+
+/**
+ * the QSQLiteResult
+ * either a SQL statusment failed or succeeded
+ */
+class QSQLiteResult
+{
+
+public:
+ typedef QValueList<QSQLiteResultRow> Columns;
+
+ /** The Status of a Result */
+ enum Status{ Success = 0, Failure, Undefined };
+
+ /**
+ * default c'tor
+ * @param status The Status of the Result
+ * @param r ResultItems
+ * @param error Error Message
+ */
+ QSQLiteResult( enum Status status = Undefined,
+ const Columns &r = Columns(),
+ const QString &error = 0L );
+ ~QSQLiteResult();
+
+ Status getStatus() const;
+ Columns getResults() const;
+ QString getError() const;
+
+ void setStatus( enum Status status );
+ void setResults( const Columns &result );
+ void setError( const QString &error );
+
+ void addRow( QSQLiteResultRow row );
+
+ QSQLiteResultRow first();
+ QSQLiteResultRow next();
+ bool atEnd();
+
+ Columns::ConstIterator iterator() const;
+ int size() const;
+
+private:
+ enum Status m_status;
+ Columns m_list;
+ QString m_error;
+ Columns::Iterator it;
+};
+
+#endif
diff --git a/krecipes/src/backends/SQLite/literecipedb.cpp b/krecipes/src/backends/SQLite/literecipedb.cpp
new file mode 100644
index 0000000..ccd88ce
--- /dev/null
+++ b/krecipes/src/backends/SQLite/literecipedb.cpp
@@ -0,0 +1,1034 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "literecipedb.h"
+
+#include <qbuffer.h>
+
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SQLITE3
+#include <sqlite3.h>
+#elif HAVE_SQLITE
+#include <sqlite.h>
+#endif
+
+//keep these two around for porting old databases
+int sqlite_decode_binary( const unsigned char *in, unsigned char *out );
+QString escape( const QString &s );
+
+LiteRecipeDB::LiteRecipeDB( const QString &_dbFile ) : QSqlRecipeDB( QString::null, QString::null, QString::null, _dbFile )
+{
+/* KConfig * config = KGlobal::config();
+ config->setGroup( "Server" );
+
+ if ( dbFile.isNull() )
+ dbFile = config->readEntry( "DBFile", locateLocal ( "appdata", DB_FILENAME ) );
+*/
+}
+
+LiteRecipeDB::~LiteRecipeDB()
+{
+}
+
+int LiteRecipeDB::lastInsertID()
+{
+ int lastID = -1;
+ QSqlQuery query("SELECT lastInsertID()",database);
+ if ( query.isActive() && query.first() )
+ lastID = query.value(0).toInt();
+
+ //kdDebug()<<"lastInsertID(): "<<lastID<<endl;
+
+ return lastID;
+}
+
+QStringList LiteRecipeDB::backupCommand() const
+{
+ #if HAVE_SQLITE
+ QString binary = "sqlite";
+ #elif HAVE_SQLITE3
+ QString binary = "sqlite3";
+ #endif
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Server" );
+ binary = config->readEntry( "SQLitePath", binary );
+
+ QStringList command;
+ command<<binary<<database->databaseName()<<".dump";
+ return command;
+}
+
+QStringList LiteRecipeDB::restoreCommand() const
+{
+ #if HAVE_SQLITE
+ QString binary = "sqlite";
+ #elif HAVE_SQLITE3
+ QString binary = "sqlite3";
+ #endif
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Server" );
+ binary = config->readEntry( "SQLitePath", binary );
+
+ QStringList command;
+ command<<binary<<database->databaseName();
+ return command;
+}
+
+void LiteRecipeDB::createDB()
+{
+ //The file is created by SQLite automatically
+}
+
+void LiteRecipeDB::createTable( const QString &tableName )
+{
+
+ QStringList commands;
+
+ if ( tableName == "recipes" )
+ commands << QString( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(%1), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo BLOB, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP, PRIMARY KEY (id));" ).arg( maxRecipeTitleLength() );
+
+ else if ( tableName == "ingredients" )
+ commands << QString( "CREATE TABLE ingredients (id INTEGER NOT NULL, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxIngredientNameLength() );
+
+ else if ( tableName == "ingredient_list" ) {
+ commands << "CREATE TABLE ingredient_list (id INTEGER NOT NULL, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, substitute_for INTEGER, PRIMARY KEY(id) );"
+ << "CREATE index ridil_index ON ingredient_list(recipe_id);"
+ << "CREATE index iidil_index ON ingredient_list(ingredient_id);"
+ << "CREATE index gidil_index ON ingredient_list(group_id);";
+ }
+
+ else if ( tableName == "unit_list" )
+ commands << "CREATE TABLE unit_list (ingredient_id INTEGER, unit_id INTEGER);";
+
+ else if ( tableName == "units" )
+ commands << QString( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(%1), name_abbrev VARCHAR(%2), plural VARCHAR(%3), plural_abbrev VARCHAR(%4), type INTEGER NOT NULL DEFAULT '0',PRIMARY KEY (id));" )
+ .arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() ).arg( maxUnitNameLength() );
+
+ else if ( tableName == "prep_methods" )
+ commands << QString( "CREATE TABLE prep_methods (id INTEGER NOT NULL, name VARCHAR(%1), PRIMARY KEY (id));" ).arg( maxPrepMethodNameLength() );
+
+ else if ( tableName == "prep_method_list" ) {
+ commands << "CREATE TABLE prep_method_list (ingredient_list_id INTEGER NOT NULL,prep_method_id INTEGER NOT NULL, order_index INTEGER );"
+ << "CREATE index iid_index ON prep_method_list(ingredient_list_id);"
+ << "CREATE index pid_index ON prep_method_list(prep_method_id);";
+ }
+
+ else if ( tableName == "ingredient_info" )
+ commands << "CREATE TABLE ingredient_info (ingredient_id INTEGER, property_id INTEGER, amount FLOAT, per_units INTEGER);";
+
+ else if ( tableName == "ingredient_properties" )
+ commands << "CREATE TABLE ingredient_properties (id INTEGER NOT NULL,name VARCHAR(20), units VARCHAR(20), PRIMARY KEY (id));";
+
+ else if ( tableName == "ingredient_weights" ) {
+ commands << "CREATE TABLE ingredient_weights (id INTEGER NOT NULL, ingredient_id INTEGER NOT NULL, amount FLOAT, unit_id INTEGER, weight FLOAT, weight_unit_id INTEGER, prep_method_id INTEGER, PRIMARY KEY (id) );"
+
+ << "CREATE index weight_wid_index ON ingredient_weights(weight_unit_id)"
+ << "CREATE index weight_pid_index ON ingredient_weights(prep_method_id)"
+ << "CREATE index weight_uid_index ON ingredient_weights(unit_id)"
+ << "CREATE index weight_iid_index ON ingredient_weights(ingredient_id)";
+ }
+
+ else if ( tableName == "units_conversion" )
+ commands << "CREATE TABLE units_conversion (unit1_id INTEGER, unit2_id INTEGER, ratio FLOAT);";
+
+ else if ( tableName == "categories" ) {
+ commands << QString( "CREATE TABLE categories (id INTEGER NOT NULL, name varchar(%1) default NULL, parent_id INGEGER NOT NULL default -1, PRIMARY KEY (id));" ).arg( maxCategoryNameLength() );
+ commands << "CREATE index parent_id_index ON categories(parent_id);";
+ }
+ else if ( tableName == "category_list" ) {
+ commands << "CREATE TABLE category_list (recipe_id INTEGER NOT NULL,category_id INTEGER NOT NULL);"
+ << "CREATE index rid_index ON category_list(recipe_id);"
+ << "CREATE index cid_index ON category_list(category_id);";
+ }
+
+ else if ( tableName == "authors" )
+ commands << QString( "CREATE TABLE authors (id INTEGER NOT NULL, name varchar(%1) default NULL,PRIMARY KEY (id));" ).arg( maxAuthorNameLength() );
+
+ else if ( tableName == "author_list" )
+ commands << "CREATE TABLE author_list (recipe_id INTEGER NOT NULL,author_id INTEGER NOT NULL);";
+
+ else if ( tableName == "db_info" ) {
+ commands << "CREATE TABLE db_info (ver FLOAT NOT NULL,generated_by varchar(200) default NULL);";
+ commands << QString( "INSERT INTO db_info VALUES(%1,'Krecipes %2');" ).arg( latestDBVersion() ).arg( krecipes_version() );
+ }
+ else if ( tableName == "ingredient_groups" ) {
+ commands << QString( "CREATE TABLE ingredient_groups (id INTEGER NOT NULL, name varchar(%1), PRIMARY KEY (id));" ).arg( maxIngGroupNameLength() );
+ }
+ else if ( tableName == "yield_types" ) {
+ commands << QString( "CREATE TABLE yield_types (id INTEGER NOT NULL, name varchar(%1), PRIMARY KEY (id));" ).arg( maxYieldTypeLength() );
+ }
+
+ else if ( tableName == "ratings" )
+ commands << "CREATE TABLE ratings (id INTEGER NOT NULL, recipe_id int(11) NOT NULL, comment TEXT, rater TEXT, created TIMESTAMP, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criteria" )
+ commands << "CREATE TABLE rating_criteria (id INTEGER NOT NULL, name TEXT, PRIMARY KEY (id));";
+
+ else if ( tableName == "rating_criterion_list" )
+ commands << "CREATE TABLE rating_criterion_list (rating_id INTEGER NOT NULL, rating_criterion_id INTEGER, stars FLOAT);";
+
+ else
+ return ;
+
+ // execute the queries
+ for ( QStringList::const_iterator it = commands.begin(); it != commands.end(); ++it )
+ database->exec( *it );
+
+}
+
+void LiteRecipeDB::portOldDatabases( float version )
+{
+ QString command;
+ if ( qRound(version*10) < 5 ) {
+ //===========add prep_method_id to ingredient_list table
+ //There's no ALTER command in SQLite, so we have to copy all data to a new table and then recreate the table with the prep_method_id
+ database->exec( "CREATE TABLE ingredient_list_copy (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, order_index INTEGER);" );
+ QSqlQuery copyQuery( "SELECT recipe_id,ingredient_id,amount,unit_id,order_index FROM ingredient_list;", database );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO ingredient_list_copy VALUES(%1,%2,%3,%4,%5);" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( copyQuery.value( 1 ).toInt() )
+ .arg( copyQuery.value( 2 ).toDouble() )
+ .arg( copyQuery.value( 3 ).toInt() )
+ .arg( copyQuery.value( 4 ).toInt() );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list" );
+ database->exec( "CREATE TABLE ingredient_list (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER);" );
+ copyQuery = database->exec( "SELECT * FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO ingredient_list VALUES(%1,%2,%3,%4,%5,%6);" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( copyQuery.value( 1 ).toInt() )
+ .arg( copyQuery.value( 2 ).toDouble() )
+ .arg( copyQuery.value( 3 ).toInt() )
+ .arg( -1 ) //default prep method
+ .arg( copyQuery.value( 4 ).toInt() );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "CREATE index ridil_index ON ingredient_list(recipe_id);" );
+ database->exec( "CREATE index iidil_index ON ingredient_list(ingredient_id);" );
+
+
+ //==============expand length of author name to 50 characters
+ database->exec( "CREATE TABLE authors_copy (id INTEGER, name varchar(20));" );
+ copyQuery = database->exec( "SELECT * FROM authors;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO authors_copy VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE authors" );
+ database->exec( "CREATE TABLE authors (id INTEGER NOT NULL, name varchar(50) default NULL,PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM authors_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO authors VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE authors_copy" );
+
+
+ //==================expand length of category name to 40 characters
+ database->exec( "CREATE TABLE categories_copy (id INTEGER, name varchar(20));" );
+ copyQuery = database->exec( "SELECT * FROM categories;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories_copy VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories" );
+ database->exec( "CREATE TABLE categories (id INTEGER NOT NULL, name varchar(40) default NULL,PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM categories_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories_copy" );
+
+ //================Set the version to the new one (0.5)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.5,'Krecipes 0.5');";
+ database->exec( command );
+ }
+
+ if ( qRound(version*10) < 6 ) {
+ //==================add a column to 'categories' to allow subcategories
+ database->exec( "CREATE TABLE categories_copy (id INTEGER, name varchar(40));" );
+ QSqlQuery copyQuery = database->exec( "SELECT * FROM categories;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories_copy VALUES(%1,'%2');" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories" );
+ database->exec( "CREATE TABLE categories (id INTEGER NOT NULL, name varchar(40) default NULL, parent_id INTEGER NOT NULL, PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM categories_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO categories VALUES(%1,'%2',-1);" )
+ .arg( copyQuery.value( 0 ).toInt() )
+ .arg( escape( copyQuery.value( 1 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE categories_copy" );
+
+ //================Set the version to the new one (0.6)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.6,'Krecipes 0.6');";
+ database->exec( command );
+ }
+
+ if ( qRound(version*100) < 61 ) {
+ //==================add a column to 'recipes' to allow prep time
+ database->exec( "CREATE TABLE recipes_copy (id INTEGER NOT NULL,title VARCHAR(200),persons INTEGER,instructions TEXT, photo BLOB, PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT * FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO recipes_copy VALUES(%1,'%2','%3','%4','%5');" )
+ .arg( escape( copyQuery.value( 0 ).toString() ) )
+ .arg( escape( copyQuery.value( 1 ).toString() ) )
+ .arg( escape( copyQuery.value( 2 ).toString() ) )
+ .arg( escape( copyQuery.value( 3 ).toString() ) )
+ .arg( escape( copyQuery.value( 4 ).toString() ) );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(200),persons INTEGER,instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT * FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = QString( "INSERT INTO recipes VALUES(%1,'%2','%3','%4','%5',NULL);" )
+ .arg( escape( copyQuery.value( 0 ).toString() ) )
+ .arg( escape( copyQuery.value( 1 ).toString() ) )
+ .arg( escape( copyQuery.value( 2 ).toString() ) )
+ .arg( escape( copyQuery.value( 3 ).toString() ) )
+ .arg( escape( copyQuery.value( 4 ).toString() ) );
+
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ //================Set the version to the new one (0.61)
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.61,'Krecipes 0.6');";
+ database->exec( command );
+ }
+
+ if ( qRound(version*100) < 62 ) {
+ database->transaction();
+
+ //==================add a column to 'ingredient_list' to allow grouping ingredients
+ database->exec( "CREATE TABLE ingredient_list_copy (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER);" );
+ QSqlQuery copyQuery = database->exec( "SELECT * FROM ingredient_list;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list_copy VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "');";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list" );
+ database->exec( "CREATE TABLE ingredient_list (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER);" );
+ copyQuery = database->exec( "SELECT * FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "',-1)";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.62,'Krecipes 0.7');";
+ database->exec( command );
+
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 63 ) {
+ database->transaction();
+
+ //==================add a column to 'units' to allow handling plurals
+ database->exec( "CREATE TABLE units_copy (id INTEGER NOT NULL, name VARCHAR(20), PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name FROM units;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units_copy VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "');";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units" );
+ database->exec( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(20), plural VARCHAR(20), PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT id,name FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units VALUES('" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "',NULL)";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+ QSqlQuery result = database->exec( "SELECT id,name FROM units WHERE plural ISNULL;" );
+ if ( result.isActive() ) {
+ while ( result.next() ) {
+ command = "UPDATE units SET plural='" + escape( result.value( 1 ).toString() ) + "' WHERE id=" + QString::number( result.value( 0 ).toInt() );
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+
+ command = "DELETE FROM db_info;"; // Remove previous version records if they exist
+ database->exec( command );
+ command = "INSERT INTO db_info VALUES(0.63,'Krecipes 0.7');";
+ database->exec( command );
+
+ database->commit();
+ }
+
+ if ( qRound(version*10) < 7 ) { //simply call 0.63 -> 0.7
+ database->exec( "UPDATE db_info SET ver='0.7';" );
+ }
+
+ if ( qRound(version*100) < 81 ) {
+ database->transaction();
+ addColumn("CREATE TABLE %1 (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, %2 unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER)","amount_offset FLOAT","'0'","ingredient_list",3);
+
+ //addColumn() doesn't preserve indexes
+ database->exec("CREATE index ridil_index ON ingredient_list(recipe_id)");
+ database->exec("CREATE index iidil_index ON ingredient_list(ingredient_id)");
+
+ database->exec( "UPDATE db_info SET ver='0.81',generated_by='Krecipes SVN (20050816)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 82 ) {
+ database->transaction();
+
+ //==================add a columns to 'recipes' to allow yield range + yield type
+ database->exec( "CREATE TABLE recipes_copy (id INTEGER NOT NULL,title VARCHAR(200),persons INTEGER,instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //title
+ + "','" + escape( copyQuery.value( 2 ).toString() ) //persons
+ + "','" + escape( copyQuery.value( 3 ).toString() ) //instructions
+ + "','" + escape( copyQuery.value( 4 ).toString() ) //photo
+ + "','" + escape( copyQuery.value( 5 ).toString() ) //prep_time
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(200), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER, instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ copyQuery = database->exec( "SELECT id,title,persons,instructions,photo,prep_time FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //title
+ + "','" + escape( copyQuery.value( 2 ).toString() ) //persons, now yield_amount
+ + "','0" //yield_amount_offset
+ + "','-1" //yield_type_id
+ + "','" + escape( copyQuery.value( 3 ).toString() ) //instructions
+ + "','" + escape( copyQuery.value( 4 ).toString() ) //photo
+ + "','" + escape( copyQuery.value( 5 ).toString() ) //prep_time
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.82',generated_by='Krecipes SVN (20050902)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 83 ) {
+ database->transaction();
+
+ //====add a id columns to 'ingredient_list' to identify it for the prep method list
+ database->exec( "CREATE TABLE ingredient_list_copy (recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, prep_method_id INTEGER, order_index INTEGER, group_id INTEGER);" );
+ QSqlQuery copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list" );
+ database->exec( "CREATE TABLE ingredient_list (id INTEGER NOT NULL, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, PRIMARY KEY(id) );" );
+
+ copyQuery = database->exec( "SELECT recipe_id,ingredient_id,amount,amount_offset,unit_id,prep_method_id,order_index,group_id FROM ingredient_list_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO ingredient_list VALUES("
+ + QString("NULL")
+ + ",'" + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "')";
+ database->exec( command );
+
+ int prep_method_id = copyQuery.value( 5 ).toInt();
+ if ( prep_method_id != -1 ) {
+ command = "INSERT INTO prep_method_list VALUES('"
+ + QString::number(lastInsertID())
+ + "','" + QString::number(prep_method_id)
+ + "','1" //order_index
+ + "')";
+ database->exec( command );
+ }
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE ingredient_list_copy" );
+
+ database->exec( "CREATE INDEX ridil_index ON ingredient_list(recipe_id);" );
+ database->exec( "CREATE INDEX iidil_index ON ingredient_list(ingredient_id);" );
+
+ database->exec( "UPDATE db_info SET ver='0.83',generated_by='Krecipes SVN (20050909)';" );
+
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 84 ) {
+ database->transaction();
+
+ //==================add a columns to 'recipes' to allow storing atime, mtime, ctime
+ database->exec( "CREATE TABLE recipes_copy (id INTEGER NOT NULL,title VARCHAR(200), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER, instructions TEXT, photo BLOB, prep_time TIME, PRIMARY KEY (id));" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,title,yield_amount,yield_amount_offset,yield_type_id,instructions,photo,prep_time FROM recipes;" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes" );
+ database->exec( "CREATE TABLE recipes (id INTEGER NOT NULL,title VARCHAR(200), yield_amount FLOAT, yield_amount_offset FLOAT, yield_type_id INTEGER DEFAULT '-1', instructions TEXT, photo BLOB, prep_time TIME, ctime TIMESTAMP, mtime TIMESTAMP, atime TIMESTAMP, PRIMARY KEY (id))" );
+ copyQuery = database->exec( "SELECT id,title,yield_amount,yield_amount_offset,yield_type_id,instructions,photo,prep_time FROM recipes_copy" );
+ if ( copyQuery.isActive() ) {
+
+ QString current_timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO recipes VALUES('"
+ + escape( copyQuery.value( 0 ).toString() )
+ + "','" + escape( copyQuery.value( 1 ).toString() )
+ + "','" + escape( copyQuery.value( 2 ).toString() )
+ + "','" + escape( copyQuery.value( 3 ).toString() )
+ + "','" + escape( copyQuery.value( 4 ).toString() )
+ + "','" + escape( copyQuery.value( 5 ).toString() )
+ + "','" + escape( copyQuery.value( 6 ).toString() )
+ + "','" + escape( copyQuery.value( 7 ).toString() )
+ + "','" + escape( current_timestamp ) //ctime
+ + "','" + escape( current_timestamp ) //mtime
+ + "','" + escape( current_timestamp ) //atime
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE recipes_copy" );
+
+ database->exec( "UPDATE db_info SET ver='0.84',generated_by='Krecipes SVN (20050913)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 85 ) {
+ database->transaction();
+
+ QSqlQuery query( "SELECT id,photo FROM recipes", database );
+
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QImage photo;
+ QString photoString = query.value(1).toString();
+
+ // Decode the photo
+ uchar *photoArray = new uchar [ photoString.length() + 1 ];
+ memcpy( photoArray, photoString.latin1(), photoString.length() * sizeof( char ) );
+ sqlite_decode_binary( ( uchar* ) photoArray, ( uchar* ) photoArray );
+
+ photo.loadFromData( photoArray, photoString.length() );
+
+ // picture will now have a ready-to-use image
+ delete[] photoArray;
+
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ QImageIO iio( &buffer, "JPEG" );
+ iio.setImage( photo );
+ iio.write();
+ //recipe->photo.save( &buffer, "JPEG" ); don't need QImageIO in QT 3.2
+ storePhoto( query.value(0).toInt(), ba );
+
+ emit progress();
+ }
+ }
+
+
+ database->exec( "UPDATE db_info SET ver='0.85',generated_by='Krecipes SVN (20050926)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 86 ) {
+ database->transaction();
+
+ database->exec( "CREATE index gidil_index ON ingredient_list(group_id)" );
+
+ QSqlQuery query( "SELECT id,name FROM ingredient_groups ORDER BY name", database );
+
+ QString last;
+ int lastID;
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ QString name = query.value(1).toString();
+ int id = query.value(0).toInt();
+ if ( last == name ) {
+ QString command = QString("UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2").arg(lastID).arg(id);
+ database->exec(command);
+
+ command = QString("DELETE FROM ingredient_groups WHERE id=%1").arg(id);
+ database->exec(command);
+ }
+ last = name;
+ lastID = id;
+
+ emit progress();
+ }
+ }
+
+ database->exec( "UPDATE db_info SET ver='0.86',generated_by='Krecipes SVN (20050928)'" );
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.86 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 87 ) {
+ //Load this default data so the user knows what rating criteria is
+ database->exec( QString("INSERT INTO rating_criteria VALUES (1,'%1')").arg(i18n("Overall")) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (2,'%1')").arg(i18n("Taste") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (3,'%1')").arg(i18n("Appearance") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (4,'%1')").arg(i18n("Originality") ) );
+ database->exec( QString("INSERT INTO rating_criteria VALUES (5,'%1')").arg(i18n("Ease of Preparation") ) );
+
+ database->exec( "UPDATE db_info SET ver='0.87',generated_by='Krecipes SVN (20051014)'" );
+ }
+
+ if ( qRound(version*100) < 90 ) {
+ database->exec("UPDATE db_info SET ver='0.9',generated_by='Krecipes 0.9'");
+ }
+
+ if ( qRound(version*100) < 91 ) {
+ database->exec("CREATE index parent_id_index ON categories(parent_id)");
+ database->exec("UPDATE db_info SET ver='0.91',generated_by='Krecipes SVN (20060526)'");
+ }
+
+ if ( qRound(version*100) < 92 ) {
+ database->transaction();
+
+ //==================add a columns to 'units' to allow unit abbreviations
+ database->exec( "CREATE TABLE units_copy (id INTEGER NOT NULL, name VARCHAR(20), plural VARCHAR(20))" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name,plural FROM units" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "','" + escape( copyQuery.value( 2 ).toString() ) //plural
+ + "')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units" );
+ database->exec( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(20), name_abbrev VARCHAR(20), plural VARCHAR(20), plural_abbrev VARCHAR(20), PRIMARY KEY (id))" );
+ copyQuery = database->exec( "SELECT id,name,plural FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ command = "INSERT INTO units VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "',NULL" //name_abbrev
+ + ",'" + escape( copyQuery.value( 2 ).toString() ) //plural
+ + "',NULL" //plural_abbrev
+ + ")";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+ database->exec("UPDATE db_info SET ver='0.92',generated_by='Krecipes SVN (20060609)'");
+ if ( !database->commit() )
+ kdDebug()<<"Update to 0.92 failed. Maybe you should try again."<<endl;
+ }
+
+ if ( qRound(version*100) < 93 ) {
+ database->transaction();
+
+ addColumn("CREATE TABLE %1 (id INTEGER NOT NULL, recipe_id INTEGER, ingredient_id INTEGER, amount FLOAT, amount_offset FLOAT, unit_id INTEGER, order_index INTEGER, group_id INTEGER, %2 PRIMARY KEY(id) )","substitute_for INTEGER","NULL","ingredient_list",8);
+
+ database->exec( "CREATE index ridil_index ON ingredient_list(recipe_id)" );
+ database->exec( "CREATE index iidil_index ON ingredient_list(ingredient_id)" );
+ database->exec( "CREATE index gidil_index ON ingredient_list(group_id)" );
+
+ database->exec( "UPDATE db_info SET ver='0.93',generated_by='Krecipes SVN (20060616)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 94 ) {
+ database->transaction();
+
+ //==================add a column to 'units' to allow specifying a type
+ database->exec( "CREATE TABLE units_copy (id INTEGER NOT NULL, name VARCHAR(20), name_abbrev VARCHAR(20), plural VARCHAR(20), plural_abbrev VARCHAR(20))" );
+ QSqlQuery copyQuery = database->exec( "SELECT id,name,name_abbrev,plural,plural_abbrev FROM units" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ QString name_abbrev = escape( copyQuery.value( 2 ).toString() );
+ if ( name_abbrev.isEmpty() )
+ name_abbrev = "NULL";
+ else {
+ name_abbrev.prepend("'");
+ name_abbrev.append("'");
+ }
+ QString plural_abbrev = escape( copyQuery.value( 4 ).toString() );
+ if ( plural_abbrev.isEmpty() )
+ plural_abbrev = "NULL";
+ else {
+ plural_abbrev.prepend("'");
+ plural_abbrev.append("'");
+ }
+
+ command = "INSERT INTO units_copy VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "'," + name_abbrev //name_abbrev
+ + ",'" + escape( copyQuery.value( 3 ).toString() ) //plural
+ + "'," + plural_abbrev //plural_abbrev
+ + ")";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units" );
+ database->exec( "CREATE TABLE units (id INTEGER NOT NULL, name VARCHAR(20), name_abbrev VARCHAR(20), plural VARCHAR(20), plural_abbrev VARCHAR(20), type INTEGER NOT NULL, PRIMARY KEY (id))" );
+ copyQuery = database->exec( "SELECT id,name,name_abbrev,plural,plural_abbrev FROM units_copy" );
+ if ( copyQuery.isActive() ) {
+
+ while ( copyQuery.next() ) {
+ QString name_abbrev = escape( copyQuery.value( 2 ).toString() );
+ if ( name_abbrev.isEmpty() )
+ name_abbrev = "NULL";
+ else {
+ name_abbrev.prepend("'");
+ name_abbrev.append("'");
+ }
+ QString plural_abbrev = escape( copyQuery.value( 4 ).toString() );
+ if ( plural_abbrev.isEmpty() )
+ plural_abbrev = "NULL";
+ else {
+ plural_abbrev.prepend("'");
+ plural_abbrev.append("'");
+ }
+
+ command = "INSERT INTO units VALUES('"
+ + escape( copyQuery.value( 0 ).toString() ) //id
+ + "','" + escape( copyQuery.value( 1 ).toString() ) //name
+ + "'," + name_abbrev //name_abbrev
+ + ",'" + escape( copyQuery.value( 3 ).toString() ) //plural
+ + "'," + plural_abbrev //plural_abbrev
+ + ",'0')";
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE units_copy" );
+
+
+ database->exec( "UPDATE db_info SET ver='0.94',generated_by='Krecipes SVN (20060712)';" );
+ database->commit();
+ }
+
+ if ( qRound(version*100) < 95 ) {
+ database->exec( "DROP TABLE ingredient_weights" );
+ createTable( "ingredient_weights" );
+ database->exec( "UPDATE db_info SET ver='0.95',generated_by='Krecipes SVN (20060726)'" );
+ }
+}
+
+void LiteRecipeDB::addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index )
+{
+ QString command;
+
+ command = QString(new_table_sql).arg(table_name+"_copy").arg(QString::null);
+ kdDebug()<<"calling: "<<command<<endl;
+ database->exec( command );
+
+ command = "SELECT * FROM "+table_name;
+ kdDebug()<<"calling: "<<command<<endl;
+ QSqlQuery copyQuery = database->exec( command );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ if ( copyQuery.value(i).isNull() )
+ break;
+
+ QString data = copyQuery.value(i).toString();
+
+ dataList << "'"+escape(data)+"'";
+ }
+ command = "INSERT INTO "+table_name+"_copy VALUES("+dataList.join(",")+");";
+ kdDebug()<<"calling: "<<command<<endl;
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE "+table_name );
+ database->exec( QString(new_table_sql).arg(table_name).arg(new_col_info+",") );
+ copyQuery = database->exec( "SELECT * FROM "+table_name+"_copy" );
+ if ( copyQuery.isActive() ) {
+ while ( copyQuery.next() ) {
+ QStringList dataList;
+ for ( int i = 0 ;; ++i ) {
+ if ( i == col_index )
+ dataList << default_value;
+
+ if ( copyQuery.value(i).isNull() )
+ break;
+
+ QString data = copyQuery.value(i).toString();
+
+ dataList << "'"+escape(data)+"'";
+ }
+ command = "INSERT INTO "+table_name+" VALUES(" +dataList.join(",")+")";
+ kdDebug()<<"calling: "<<command<<endl;
+ database->exec( command );
+
+ emit progress();
+ }
+ }
+ database->exec( "DROP TABLE "+table_name+"_copy" );
+}
+
+QString LiteRecipeDB::escapeAndEncode( const QString &s ) const
+{
+ QString s_escaped;
+
+ // Escape
+ s_escaped = escape( QString::fromLatin1(s.utf8()) );
+
+ // Return encoded
+ return s_escaped.latin1(); // Note that the text has already been converted before escaping.
+}
+
+/*
+** Decode the string "in" into binary data and write it into "out".
+** This routine reverses the encoding created by sqlite_encode_binary().
+** The output will always be a few bytes less than the input. The number
+** of bytes of output is returned. If the input is not a well-formed
+** encoding, -1 is returned.
+**
+** The "in" and "out" parameters may point to the same buffer in order
+** to decode a string in place.
+*/
+int sqlite_decode_binary( const unsigned char *in, unsigned char *out )
+{
+ int i, c, e;
+ e = *( in++ );
+ i = 0;
+ while ( ( c = *( in++ ) ) != 0 ) {
+ if ( c == 1 ) {
+ c = *( in++ );
+ if ( c == 1 ) {
+ c = 0;
+ }
+ else if ( c == 2 ) {
+ c = 1;
+ }
+ else if ( c == 3 ) {
+ c = '\'';
+ }
+ else {
+ return -1;
+ }
+ }
+ out[ i++ ] = ( c + e ) & 0xff;
+ }
+ return i;
+}
+
+QString escape( const QString &s )
+{
+ QString s_escaped = s;
+
+ if ( !s_escaped.isEmpty() ) { //###: sqlite_mprintf() seems to fill an empty string with garbage
+ // Escape using SQLite's function
+#if HAVE_SQLITE
+ char * escaped = sqlite_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#elif HAVE_SQLITE3
+ char * escaped = sqlite3_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#endif
+ s_escaped = escaped;
+#if HAVE_SQLITE
+ sqlite_freemem( escaped ); // free allocated memory
+#elif HAVE_SQLITE3
+ sqlite3_free( escaped ); // free allocated memory
+#endif
+ }
+
+ return ( s_escaped );
+}
+
+#include "literecipedb.moc"
diff --git a/krecipes/src/backends/SQLite/literecipedb.h b/krecipes/src/backends/SQLite/literecipedb.h
new file mode 100644
index 0000000..c83672f
--- /dev/null
+++ b/krecipes/src/backends/SQLite/literecipedb.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef LITERECIPEDB_H
+#define LITERECIPEDB_H
+
+#include "backends/qsqlrecipedb.h"
+
+#include <qstring.h>
+
+#include "qsql_sqlite.h"
+
+#define SQLITE_DRIVER "KRESQLITE"
+
+class LiteRecipeDB : public QSqlRecipeDB
+{
+
+ Q_OBJECT
+
+private:
+ void createDB( void );
+
+public:
+ LiteRecipeDB( const QString &DBName = DEFAULT_DB_NAME );
+ ~LiteRecipeDB( void );
+
+ virtual int lastInsertID();
+
+ virtual void createTable( const QString &tableName );
+ virtual void givePermissions(const QString&, const QString&, const QString&, const QString&){} //no permissions in this backend
+
+protected:
+ virtual QSqlDriver *qsqlDriver() const
+ {
+ return new KreSQLiteDriver();
+ }
+
+ virtual QString escapeAndEncode( const QString &s ) const;
+
+private:
+ virtual void portOldDatabases( float version );
+ virtual QStringList backupCommand() const;
+ virtual QStringList restoreCommand() const;
+
+ void addColumn( const QString &new_table_sql, const QString &new_col_info, const QString &default_value, const QString &table_name, int col_index );
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/SQLite/qsql_sqlite.cpp b/krecipes/src/backends/SQLite/qsql_sqlite.cpp
new file mode 100644
index 0000000..b39a10b
--- /dev/null
+++ b/krecipes/src/backends/SQLite/qsql_sqlite.cpp
@@ -0,0 +1,459 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "qsql_sqlite.h"
+
+#include <qdatetime.h>
+#include <qregexp.h>
+#include <qfile.h>
+
+#include <qptrvector.h>
+#include <unistd.h>
+
+#include <kdebug.h>
+
+#include "libqsqlite/krecqsqlitedb.h"
+#include "libqsqlite/krecqsqliteresult.h"
+
+#define QSQLITE_DRIVER_NAME "KRE_QSQLITE"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_SQLITE3
+#include <sqlite3.h>
+#define sqlite_free sqlite3_free
+#elif HAVE_SQLITE
+#include <sqlite.h>
+#endif
+
+class KreSQLiteResult : public QSqlResult
+{
+public:
+ KreSQLiteResult( const KreSQLiteDriver *d ): QSqlResult( d )
+ {
+ db = d->db;
+ }
+ ~KreSQLiteResult() {}
+protected:
+ QVariant data( int );
+ bool reset ( const QString& );
+ bool fetch( int );
+ bool fetchFirst();
+ bool fetchNext();
+ bool fetchLast();
+ bool isNull( int );
+ QSqlRecord record();
+ int size();
+ int numRowsAffected();
+
+private:
+ QSQLiteResult result;
+ QSQLiteResultRow row;
+
+ QSQLiteDB *db;
+};
+
+
+QVariant KreSQLiteResult::data( int field )
+{
+ if ( !isSelect() ) {
+ //qWarning( "KreSQLiteResult::data: column %d out of range", field );
+ qWarning( "KreSQLiteResult::data: not a select statement" );
+ return QVariant();
+ }
+
+ return QVariant(row.data(field));
+}
+
+bool KreSQLiteResult::fetch( int i )
+{
+kdDebug()<<"fetch_i"<<endl;
+ if ( isForwardOnly() ) { // fake a forward seek
+ if ( at() < i ) {
+ int x = i - at();
+ while ( --x && fetchNext() );
+ return fetchNext();
+ } else {
+ return false;
+ }
+ }
+ if ( at() == i )
+ return true;
+
+ row = result.first();
+ for ( int j = 0; j < i; ++j ) {
+ if ( result.atEnd() )
+ return false;
+
+ row = result.next();
+ }
+
+ setAt( i );
+ return true;
+}
+
+bool KreSQLiteResult::fetchNext()
+{
+ row = result.next();
+
+ if ( result.atEnd() )
+ return false;
+
+ setAt( at() + 1 );
+ return true;
+}
+
+bool KreSQLiteResult::fetchFirst()
+{
+ row = result.first();
+
+ if ( result.atEnd() )
+ return false;
+
+ setAt(0);
+ return true;
+}
+
+bool KreSQLiteResult::fetchLast()
+{kdDebug()<<"fetchlast"<<endl;
+ if ( isForwardOnly() ) { // fake this since MySQL can't seek on forward only queries
+ bool success = fetchNext(); // did we move at all?
+ while ( fetchNext() );
+ return success;
+ }
+ int numRows = size();
+ if ( !numRows )
+ return false;
+ return fetch( numRows - 1 );
+}
+
+bool KreSQLiteResult::isNull( int i )
+{
+ return false;
+}
+
+QSqlRecord KreSQLiteResult::record()
+{kdDebug()<<"record"<<endl;
+ return QSqlRecord();
+}
+
+int KreSQLiteResult::size()
+{
+//kdDebug()<<"size: "<<result.size()<<endl;
+ return result.size();
+}
+
+int KreSQLiteResult::numRowsAffected()
+{kdDebug()<<"numrowsaffected"<<endl;
+ return 1;/*sqlite3_changes(db)*/
+}
+
+/*
+ Execute \a query.
+*/
+bool KreSQLiteResult::reset(const QString& query)
+{
+ // this is where we build a query.
+ if (!driver())
+ return false;
+ if (!driver()-> isOpen() || driver()->isOpenError())
+ return false;
+
+ //cleanup
+ setAt( -1 );
+
+ setSelect(true);
+
+
+ result = db->executeQuery( query );
+ int res = result.getStatus();
+
+ if (res != SQLITE_OK ) {
+ setLastError(QSqlError("Unable to execute statement", result.getError(), QSqlError::Statement, res));
+ }
+
+ setActive(true);
+ return true;
+}
+
+/////////////////////////////////////////////////////////
+
+KreSQLiteDriver::KreSQLiteDriver(QObject * parent, const char * name)
+ : QSqlDriver(parent, name ? name : QSQLITE_DRIVER_NAME)
+{
+}
+
+KreSQLiteDriver::KreSQLiteDriver(QSQLiteDB *connection, QObject *parent, const char *name)
+ : QSqlDriver(parent, name ? name : QSQLITE_DRIVER_NAME)
+{
+ db = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+
+KreSQLiteDriver::~KreSQLiteDriver()
+{
+}
+
+bool KreSQLiteDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case QuerySize:
+ case Transactions:
+ return true;
+ // case BLOB:
+ default:
+ return false;
+ }
+}
+
+/*
+ SQLite dbs have no user name, passwords, hosts or ports.
+ just file names.
+*/
+bool KreSQLiteDriver::open(const QString & file, const QString &, const QString &, const QString &, int)
+{
+ if (isOpen())
+ close();
+
+ if (file.isEmpty())
+ return false;
+
+ db = new QSQLiteDB;
+ if ( !db->open(QFile::encodeName(file)) ) {
+ setLastError(QSqlError("Error to open database", 0, QSqlError::Connection));
+ setOpenError(true);
+ setOpen(false);
+ return false;
+ }
+
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void KreSQLiteDriver::close()
+{
+ if (isOpen()) {
+ db->close();
+ delete db; db = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlQuery KreSQLiteDriver::createQuery() const
+{
+ return QSqlQuery(new KreSQLiteResult(this));
+}
+
+bool KreSQLiteDriver::beginTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSQLiteResult result = db->executeQuery( "BEGIN" );
+ int status = result.getStatus();
+ if (status == QSQLiteResult::Success)
+ return true;
+
+ setLastError(QSqlError("Unable to begin transaction", result.getError(), QSqlError::Transaction, status));
+ return false;
+}
+
+bool KreSQLiteDriver::commitTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSQLiteResult result = db->executeQuery( "COMMIT" );
+ int status = result.getStatus();
+ if (status == QSQLiteResult::Success)
+ return true;
+
+ setLastError(QSqlError("Unable to commit transaction", result.getError(), QSqlError::Transaction, status));
+ return false;
+}
+
+bool KreSQLiteDriver::rollbackTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSQLiteResult result = db->executeQuery( "ROLLBACK" );
+ int status = result.getStatus();
+ if (status == SQLITE_OK)
+ return true;
+
+ setLastError(QSqlError("Unable to rollback transaction", result.getError(), QSqlError::Transaction, status));
+ return false;
+}
+
+QStringList KreSQLiteDriver::tables(const QString &typeName) const
+{
+ QStringList res;
+ if (!isOpen())
+ return res;
+
+ int type = typeName.toInt();
+
+ QSqlQuery q = createQuery();
+ q.setForwardOnly(true);
+ #if (QT_VERSION-0 >= 0x030000)
+ if ((type & (int)QSql::Tables) && (type & (int)QSql::Views))
+ q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'");
+ else if (typeName.isEmpty() || (type & (int)QSql::Tables))
+ q.exec("SELECT name FROM sqlite_master WHERE type='table'");
+ else if (type & (int)QSql::Views)
+ q.exec("SELECT name FROM sqlite_master WHERE type='view'");
+ #else
+ q.exec("SELECT name FROM sqlite_master WHERE type='table' OR type='view'");
+ #endif
+
+ if (q.isActive()) {
+ while(q.next()) {
+ res.append(q.value(0).toString());
+ }
+ }
+
+ #if (QT_VERSION-0 >= 0x030000)
+ if (type & (int)QSql::SystemTables) {
+ // there are no internal tables beside this one:
+ res.append("sqlite_master");
+ }
+ #endif
+
+ return res;
+}
+
+QSqlIndex KreSQLiteDriver::primaryIndex(const QString &tblname) const
+{
+ QSqlRecordInfo rec(recordInfo(tblname)); // expensive :(
+
+ if (!isOpen())
+ return QSqlIndex();
+
+ QSqlQuery q = createQuery();
+ q.setForwardOnly(true);
+ // finrst find a UNIQUE INDEX
+ q.exec("PRAGMA index_list('" + tblname + "');");
+ QString indexname;
+ while(q.next()) {
+ if (q.value(2).toInt()==1) {
+ indexname = q.value(1).toString();
+ break;
+ }
+ }
+ if (indexname.isEmpty())
+ return QSqlIndex();
+
+ q.exec("PRAGMA index_info('" + indexname + "');");
+
+ QSqlIndex index(tblname, indexname);
+ while(q.next()) {
+ QString name = q.value(2).toString();
+ QSqlVariant::Type type = QSqlVariant::Invalid;
+ if (rec.contains(name))
+ type = rec.find(name).type();
+ index.append(QSqlField(name, type));
+ }
+ return index;
+}
+
+#if 0
+QSqlRecordInfo KreSQLiteDriver::recordInfo(const QString &tbl) const
+{
+ if (!isOpen())
+ return QSqlRecordInfo();
+
+ QSqlQuery q = createQuery();
+ q.setForwardOnly(true);
+ q.exec("SELECT * FROM " + tbl + " LIMIT 1");
+return QSqlRecordInfo();
+// return recordInfo(q);
+}
+
+QSqlRecord KreSQLiteDriver::record(const QString &tblname) const
+{
+ if (!isOpen())
+ return QSqlRecord();
+
+ return recordInfo(tblname).toRecord();
+}
+
+QSqlRecord KreSQLiteDriver::record(const QSqlQuery& query) const
+{
+ if (query.isActive() && query.driver() == this) {
+ KreSQLiteResult* result = (KreSQLiteResult*)query.result();
+ return result->rInf.toRecord();
+ }
+ return QSqlRecord();
+}
+
+QSqlRecordInfo KreSQLiteDriver::recordInfo(const QSqlQuery& query) const
+{
+ if (query.isActive() && query.driver() == this) {
+ KreSQLiteResult* result = (KreSQLiteResult*)query.result();
+ return result->rInf;
+ }
+ return QSqlRecordInfo();
+}
+
+//this would be used below in formatValue()
+static QString escape( const QString &s )
+{
+ QString s_escaped = s;
+
+ if ( !s_escaped.isEmpty() ) { //###: sqlite_mprintf() seems to fill an empty string with garbage
+ // Escape using SQLite's function
+#if HAVE_SQLITE
+ char * escaped = sqlite_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#elif HAVE_SQLITE3
+ char * escaped = sqlite3_mprintf( "%q", s.latin1() ); // Escape the string(allocates memory)
+#endif
+ s_escaped = escaped;
+#if HAVE_SQLITE
+ sqlite_freemem( escaped ); // free allocated memory
+#elif HAVE_SQLITE3
+ sqlite3_free( escaped ); // free allocated memory
+#endif
+ }
+
+ return ( s_escaped );
+}
+
+
+// Everything is considered a string given the implementation of this driver (we don't have field info). This would ruin a QByteArray (for the photo).
+QString KreSQLiteDriver::formatValue( const QSqlField* field, bool trimStrings ) const
+{
+ QString r;
+ if ( field->isNull() ) {
+ r = nullText();
+ } else {
+ switch( field->type() ) {
+ case QVariant::String:
+ case QVariant::CString: {
+ // Escape '\' characters
+ r = QSqlDriver::formatValue( field );
+ //r = escape(r);
+ //kdDebug()<<"escaping sqlite string: "<<r<<endl;
+ break;
+ }
+ default:
+ r = QSqlDriver::formatValue( field, trimStrings );
+ }
+ }
+ return r;
+}
+#endif
+
diff --git a/krecipes/src/backends/SQLite/qsql_sqlite.h b/krecipes/src/backends/SQLite/qsql_sqlite.h
new file mode 100644
index 0000000..8d19ff2
--- /dev/null
+++ b/krecipes/src/backends/SQLite/qsql_sqlite.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef QSQL_SQLITE_H
+#define QSQL_SQLITE_H
+
+#include <qsqldriver.h>
+#include <qsqlresult.h>
+#include <qsqlrecord.h>
+#include <qsqlindex.h>
+
+typedef QVariant QSqlVariant;
+
+class QSQLiteDB;
+
+class KreSQLiteDriver : public QSqlDriver
+{
+public:
+ KreSQLiteDriver(QObject * parent = 0, const char * name = 0);
+ KreSQLiteDriver(QSQLiteDB *connection, QObject *parent = 0, const char *name = 0);
+ ~KreSQLiteDriver();
+
+ bool hasFeature( DriverFeature ) const;
+ bool open( const QString&,
+ const QString&,
+ const QString&,
+ const QString&,
+ int );
+ void close();
+ QSqlQuery createQuery() const;
+
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+ QStringList tables(const QString &typeName) const;
+ QSqlIndex primaryIndex(const QString &tblname) const;
+ //QSqlRecordInfo recordInfo(const QString &tbl) const;
+ //QSqlRecord record(const QString &tblname) const;
+ //QString formatValue( const QSqlField* field, bool trimStrings ) const;
+
+private:
+ friend class KreSQLiteResult;
+
+ QSQLiteDB *db;
+};
+#endif
diff --git a/krecipes/src/backends/progressinterface.cpp b/krecipes/src/backends/progressinterface.cpp
new file mode 100644
index 0000000..7d2a7bd
--- /dev/null
+++ b/krecipes/src/backends/progressinterface.cpp
@@ -0,0 +1,79 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "progressinterface.h"
+
+#include <qapplication.h>
+
+#include <kprogress.h>
+
+#include "recipedb.h"
+
+ProgressInterface::ProgressInterface( QWidget *parent ) : progress_dlg(0), database(0), m_rate(1), m_rate_at(0)
+{
+ slot_obj = new ProgressSlotObject(parent,this);
+}
+
+ProgressInterface::~ProgressInterface()
+{
+ listenOn(0);
+ delete slot_obj;
+}
+
+void ProgressInterface::progressBegin( int steps, const QString &caption, const QString &text, int rate )
+{
+ m_rate = rate;
+
+ progress_dlg = new KProgressDialog((QWidget*)slot_obj->parent(),0,caption,text,true);
+
+ if ( steps == 0 )
+ progress_dlg->progressBar()->setPercentageVisible(false);
+ progress_dlg->progressBar()->setTotalSteps( steps );
+}
+
+void ProgressInterface::progressDone()
+{
+ delete progress_dlg;
+ progress_dlg = 0;
+
+ m_rate_at = 0;
+}
+
+void ProgressInterface::progress()
+{
+ if ( progress_dlg->wasCancelled() ) {
+ database->cancelOperation();
+ }
+ else {
+ ++m_rate_at;
+
+ if ( m_rate_at % m_rate == 0 ) {
+ progress_dlg->progressBar()->advance(1);
+ m_rate_at = 0;
+ }
+ qApp->processEvents();
+ }
+}
+
+void ProgressInterface::listenOn( RecipeDB *db )
+{
+ if ( database)
+ database->disconnect(slot_obj);
+
+ if ( db ) {
+ slot_obj->connect( db, SIGNAL(progressBegin(int,const QString&,const QString&,int)), slot_obj, SLOT(progressBegin(int,const QString&,const QString&,int)) );
+ slot_obj->connect( db, SIGNAL(progressDone()), slot_obj, SLOT(progressDone()) );
+ slot_obj->connect( db, SIGNAL(progress()), slot_obj, SLOT(progress()) );
+ }
+
+ database = db;
+}
+
+#include "progressinterface.moc"
diff --git a/krecipes/src/backends/progressinterface.h b/krecipes/src/backends/progressinterface.h
new file mode 100644
index 0000000..53c90fe
--- /dev/null
+++ b/krecipes/src/backends/progressinterface.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PROGRESSINTERFACE_H
+#define PROGRESSINTERFACE_H
+
+#include <qobject.h>
+#include <qwidget.h>
+
+class KProgressDialog;
+
+class RecipeDB;
+class ProgressSlotObject;
+
+/** This class is used to monitor events from the database that may take
+ * awhile. Before a potentially long operation, the database will
+ * call progressBegin(), progress() a number of times, and then progressDone().
+ *
+ * This class may be subclassed to perform certain operations during long
+ * operations by implementing the three virtual functions. The default
+ * implementation displays a progress bar dialog.
+ */
+class ProgressInterface
+{
+public:
+ ProgressInterface( QWidget *parent );
+ ~ProgressInterface();
+
+ void listenOn( RecipeDB* );
+
+protected:
+ friend class ProgressSlotObject;
+
+ virtual void progressBegin(int,const QString &caption,const QString &text,int rate);
+ virtual void progressDone();
+ virtual void progress();
+
+private:
+ ProgressSlotObject *slot_obj;
+ KProgressDialog *progress_dlg;
+ RecipeDB *database;
+
+ int m_rate;
+ int m_rate_at;
+};
+
+class ProgressSlotObject : public QObject
+{
+Q_OBJECT
+
+public:
+ ProgressSlotObject( QWidget*parent, ProgressInterface *p ) : QObject(parent), pInterface(p){}
+
+public slots:
+ void progressBegin(int i,const QString &caption=QString::null,const QString &text=QString::null,int rate=1){ pInterface->progressBegin(i,caption,text,rate); }
+ void progressDone(){ pInterface->progressDone(); }
+ void progress(){ pInterface->progress(); }
+
+private:
+ ProgressInterface *pInterface;
+};
+
+#endif
diff --git a/krecipes/src/backends/qsqlrecipedb.cpp b/krecipes/src/backends/qsqlrecipedb.cpp
new file mode 100644
index 0000000..1192f43
--- /dev/null
+++ b/krecipes/src/backends/qsqlrecipedb.cpp
@@ -0,0 +1,2775 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2004-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <stdlib.h>
+
+#include "qsqlrecipedb.h"
+#include "datablocks/categorytree.h"
+#include "datablocks/rating.h"
+#include "datablocks/weight.h"
+
+#include "propertycalculator.h"
+
+#include <qbuffer.h>
+#include <qtextcodec.h>
+#include <qvariant.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kmdcodec.h>
+
+int QSqlRecipeDB::m_refCount = 0;
+
+QSqlRecipeDB::QSqlRecipeDB( const QString &host, const QString &user, const QString &pass, const QString &name, int port ) : RecipeDB(),
+ connectionName("connection" + QString::number( m_refCount+1 ))
+{
+ DBuser = user;
+ DBpass = pass;
+ DBhost = host;
+ DBname = name;
+ DBport = port;
+
+ dbOK = false; //it isn't ok until we've connect()'ed
+ ++m_refCount;
+
+ QTextCodec::setCodecForCStrings(QTextCodec::codecForName("Latin1")); //this is the default, but let's explicitly set this to be sure
+}
+
+QSqlRecipeDB::~QSqlRecipeDB()
+{
+ if ( dbOK ) {
+ database->close();
+ }
+
+ QSqlDatabase::removeDatabase( connectionName );
+ --m_refCount;
+}
+
+void QSqlRecipeDB::connect( bool create_db, bool create_tables )
+{
+ kdDebug() << i18n( "QSqlRecipeDB: Opening Database..." ) << endl;
+ kdDebug() << "Parameters: \n\thost: " << DBhost << "\n\tuser: " << DBuser << "\n\ttable: " << DBname << endl;
+
+ bool driver_found = false;
+
+ if ( qsqlDriver() ) //we're using a built-in driver
+ driver_found = true;
+ else {
+ QStringList drivers = QSqlDatabase::drivers();
+ for ( QStringList::const_iterator it = drivers.begin(); it != drivers.end(); ++it ) {
+ if ( ( *it ) == qsqlDriverPlugin() ) {
+ driver_found = true;
+ break;
+ }
+ }
+ }
+
+ if ( !driver_found ) {
+ dbErr = QString( i18n( "The Qt database plug-in (%1) is not installed. This plug-in is required for using this database backend." ) ).arg( qsqlDriverPlugin() );
+ return ;
+ }
+
+ //we need to have a unique connection name for each QSqlRecipeDB class as multiple db's may be open at once (db to db transfer)
+ if ( qsqlDriver() )
+ database = QSqlDatabase::addDatabase( qsqlDriver(), connectionName );
+ else if ( !qsqlDriverPlugin().isEmpty() )
+ database = QSqlDatabase::addDatabase( qsqlDriverPlugin(), connectionName );
+ else
+ kdDebug()<<"Fatal internal error! Backend incorrectly written!"<<endl;
+
+ database->setDatabaseName( DBname );
+ if ( !( DBuser.isNull() ) )
+ database->setUserName( DBuser );
+ if ( !( DBpass.isNull() ) )
+ database->setPassword( DBpass );
+ database->setHostName( DBhost );
+ if ( DBport > 0 )
+ database->setPort(DBport);
+
+ kdDebug() << i18n( "Parameters set. Calling db->open()" ) << endl;
+
+ if ( !database->open() ) {
+ //Try to create the database
+ if ( create_db ) {
+ kdDebug() << i18n( "Failing to open database. Trying to create it" ) << endl;
+ createDB();
+ }
+ else {
+ // Handle the error (passively)
+ dbErr = QString( i18n( "Krecipes could not open the database using the driver '%2' (with username: \"%1\"). You may not have the necessary permissions, or the server may be down." ) ).arg( DBuser ).arg( qsqlDriverPlugin() );
+ }
+
+ //Now Reopen the Database and signal & exit if it fails
+ if ( !database->open() ) {
+ QString error = i18n( "Database message: %1" ).arg( database->lastError().databaseText() );
+ kdDebug() << i18n( "Failing to open database. Exiting\n" ).latin1();
+
+ // Handle the error (passively)
+ dbErr = QString( i18n( "Krecipes could not open the database using the driver '%2' (with username: \"%1\"). You may not have the necessary permissions, or the server may be down." ) ).arg( DBuser ).arg( qsqlDriverPlugin() );
+ return ;
+ }
+ }
+
+ if ( int( qRound( databaseVersion() * 1e5 ) ) > int( qRound( latestDBVersion() * 1e5 ) ) ) { //correct for float's imprecision
+ dbErr = i18n( "This database was created with a newer version of Krecipes and cannot be opened." );
+ return ;
+ }
+
+ // Check integrity of the database (tables). If not possible, exit
+ // Because checkIntegrity() will create tables if they don't exist,
+ // we don't want to run this when creating the database. We would be
+ // logged in as another user (usually the superuser and not have ownership of the tables
+ if ( create_tables && !checkIntegrity() ) {
+ dbErr = i18n( "Failed to fix database structure.\nIf you are using SQLite, this is often caused by using an SQLite 2 database with SQLite 3 installed. If this is the case, make sure both SQLite 2 and 3 are installed, and then run 'krecipes --convert-sqlite3' to update your database to the new structure." );
+ return;
+ }
+
+ // Database was opened correctly
+ m_query = QSqlQuery( QString::null, database );
+ m_query.setForwardOnly(true);
+ dbOK = true;
+}
+
+void QSqlRecipeDB::execSQL( const QString &command )
+{
+ database->exec( command );
+}
+
+void QSqlRecipeDB::loadRecipes( RecipeList *rlist, int items, QValueList<int> ids )
+{
+ // Empty the recipe first
+ rlist->empty();
+
+ QMap <int, RecipeList::Iterator> recipeIterators; // Stores the iterator of each recipe in the list;
+
+ QString command;
+
+ QString current_timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
+
+ QStringList ids_str;
+ for ( QValueList<int>::const_iterator it = ids.begin(); it != ids.end(); ++it ) {
+ QString number_str = QString::number(*it);
+ ids_str << number_str;
+
+ if ( !(items & RecipeDB::Noatime) )
+ database->exec( "UPDATE recipes SET ctime=ctime,mtime=mtime,atime='"+current_timestamp+"' WHERE id="+number_str );
+ }
+
+ // Read title, author, yield, and instructions as specified
+ command = "SELECT id";
+ if ( items & RecipeDB::Title ) command += ",title";
+ if ( items & RecipeDB::Instructions ) command += ",instructions";
+ if ( items & RecipeDB::PrepTime ) command += ",prep_time";
+ if ( items & RecipeDB::Yield ) command += ",yield_amount,yield_amount_offset,yield_type_id";
+ command += " FROM recipes"+(ids_str.count()!=0?" WHERE id IN ("+ids_str.join(",")+")":"");
+
+ QSqlQuery recipeQuery(command,database);
+ if ( recipeQuery.isActive() ) {
+ while ( recipeQuery.next() ) {
+ int row_at = 0;
+
+ Recipe recipe;
+ recipe.recipeID = recipeQuery.value( row_at ).toInt(); ++row_at;
+
+ if ( items & RecipeDB::Title ) {
+ recipe.title = unescapeAndDecode( recipeQuery.value( row_at ).toCString() ); ++row_at;
+ }
+
+ if ( items & RecipeDB::Instructions ) {
+ recipe.instructions = unescapeAndDecode( recipeQuery.value( row_at ).toCString() ); ++row_at;
+ }
+
+ if ( items & RecipeDB::PrepTime ) {
+ recipe.prepTime = recipeQuery.value( row_at ).toTime(); ++row_at;
+ }
+
+ if ( items & RecipeDB::Yield ) {
+ recipe.yield.amount = recipeQuery.value( row_at ).toDouble(); ++row_at;
+ recipe.yield.amount_offset = recipeQuery.value( row_at ).toDouble(); ++row_at;
+ recipe.yield.type_id = recipeQuery.value( row_at ).toInt(); ++row_at;
+ if ( recipe.yield.type_id != -1 ) {
+ QString y_command = QString("SELECT name FROM yield_types WHERE id=%1;").arg(recipe.yield.type_id);
+ QSqlQuery yield_query(y_command,database);
+ if ( yield_query.isActive() && yield_query.first() )
+ recipe.yield.type = unescapeAndDecode(yield_query.value( 0 ).toCString());
+ else
+ kdDebug()<<yield_query.lastError().databaseText()<<endl;
+ }
+ }
+
+ if ( items & RecipeDB::Meta )
+ loadRecipeMetadata(&recipe);
+
+ recipeIterators[ recipe.recipeID ] = rlist->append( recipe );
+ }
+ }
+
+ // Read the ingredients
+ if ( items & RecipeDB::Ingredients ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ if ( items & RecipeDB::NamesOnly ) {
+ if ( items & IngredientAmounts )
+ command = QString( "SELECT il.ingredient_id,i.name,il.substitute_for,il.amount,il.amount_offset,u.id,u.type FROM ingredients i, ingredient_list il, units u WHERE il.recipe_id=%1 AND i.id=il.ingredient_id AND u.id=il.unit_id ORDER BY il.order_index" ).arg( (*recipe_it).recipeID );
+ else
+ command = QString( "SELECT il.ingredient_id,i.name,il.substitute_for FROM ingredients i, ingredient_list il WHERE il.recipe_id=%1 AND i.id=il.ingredient_id" ).arg( (*recipe_it).recipeID );
+ }
+ else
+ command = QString( "SELECT il.ingredient_id,i.name,il.substitute_for,il.amount,il.amount_offset,u.id,u.name,u.plural,u.name_abbrev,u.plural_abbrev,u.type,il.group_id,il.id FROM ingredients i, ingredient_list il, units u WHERE il.recipe_id=%1 AND i.id=il.ingredient_id AND u.id=il.unit_id ORDER BY il.order_index" ).arg( (*recipe_it).recipeID );
+
+ QSqlQuery ingredientQuery( command, database );
+ if ( ingredientQuery.isActive() ) {
+ RecipeList::Iterator it = recipeIterators[ (*recipe_it).recipeID ];
+ while ( ingredientQuery.next() ) {
+ Ingredient ing;
+ ing.ingredientID = ingredientQuery.value( 0 ).toInt();
+ ing.name = unescapeAndDecode( ingredientQuery.value( 1 ).toCString() );
+
+ if ( items & RecipeDB::NamesOnly ) {
+ if ( items & IngredientAmounts ) {
+ ing.amount = ingredientQuery.value( 3 ).toDouble();
+ ing.amount_offset = ingredientQuery.value( 4 ).toDouble();
+ ing.units.id = ingredientQuery.value( 5 ).toInt();
+ ing.units.type = (Unit::Type)ingredientQuery.value( 6 ).toInt();
+ }
+ }
+ else {
+ ing.amount = ingredientQuery.value( 3 ).toDouble();
+ ing.amount_offset = ingredientQuery.value( 4 ).toDouble();
+ ing.units.id = ingredientQuery.value( 5 ).toInt();
+ ing.units.name = unescapeAndDecode( ingredientQuery.value( 6 ).toCString() );
+ ing.units.plural = unescapeAndDecode( ingredientQuery.value( 7 ).toCString() );
+ ing.units.name_abbrev = unescapeAndDecode( ingredientQuery.value( 8 ).toCString() );
+ ing.units.plural_abbrev = unescapeAndDecode( ingredientQuery.value( 9 ).toCString() );
+ ing.units.type = (Unit::Type)ingredientQuery.value( 10 ).toInt();
+
+ //if we don't have both name and plural, use what we have as both
+ if ( ing.units.name.isEmpty() )
+ ing.units.name = ing.units.plural;
+ else if ( ing.units.plural.isEmpty() )
+ ing.units.plural = ing.units.name;
+
+ ing.groupID = ingredientQuery.value( 11 ).toInt();
+ if ( ing.groupID != -1 ) {
+ QSqlQuery toLoad( QString( "SELECT name FROM ingredient_groups WHERE id=%1" ).arg( ing.groupID ), database );
+ if ( toLoad.isActive() && toLoad.first() )
+ ing.group = unescapeAndDecode( toLoad.value( 0 ).toCString() );
+ }
+
+ command = QString("SELECT pl.prep_method_id,p.name FROM prep_methods p, prep_method_list pl WHERE pl.ingredient_list_id=%1 AND p.id=pl.prep_method_id ORDER BY pl.order_index;").arg(ingredientQuery.value( 12 ).toInt());
+ QSqlQuery ingPrepMethodsQuery( command, database );
+ if ( ingPrepMethodsQuery.isActive() ) {
+ while ( ingPrepMethodsQuery.next() ) {
+ ing.prepMethodList.append( Element( unescapeAndDecode(ingPrepMethodsQuery.value(1).toCString()),ingPrepMethodsQuery.value(0).toInt()) );
+ }
+ }
+ }
+
+ if ( ingredientQuery.value( 2 ).toInt() > 0 ) {
+ //given the ordering, we can assume substitute_for is the id of the last
+ //ingredient in the list
+ //int substitute_for = ingredientQuery.value( 2 ).toInt();
+ (*it).ingList.last().substitutes.append( ing );
+ }
+ else
+ (*it).ingList.append( ing );
+ }
+ }
+ }
+ }
+
+ //Load the Image
+ if ( items & RecipeDB::Photo ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+ loadPhoto( (*it).recipeID, (*it).photo );
+ }
+ }
+
+ //Load the category list
+ if ( items & RecipeDB::Categories ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+
+ command = QString( "SELECT cl.category_id,c.name FROM category_list cl, categories c WHERE recipe_id=%1 AND cl.category_id=c.id;" ).arg( (*it).recipeID );
+
+ m_query.exec( command );
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element el;
+ el.id = m_query.value( 0 ).toInt();
+ el.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ (*it).categoryList.append( el );
+ }
+ }
+ }
+ }
+
+ //Load the author list
+ if ( items & RecipeDB::Authors ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+
+ command = QString( "SELECT al.author_id,a.name FROM author_list al, authors a WHERE recipe_id=%1 AND al.author_id=a.id;" ).arg( (*it).recipeID );
+
+ m_query.exec( command );
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element el;
+ el.id = m_query.value( 0 ).toInt();
+ el.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ (*it).authorList.append( el );
+ }
+ }
+ }
+ }
+
+ //Load the ratings
+ if ( items & RecipeDB::Ratings ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+
+ command = QString( "SELECT id,comment,rater FROM ratings WHERE recipe_id=%1 ORDER BY created DESC" ).arg( (*it).recipeID );
+ QSqlQuery query( command, database );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Rating r;
+ r.id = query.value( 0 ).toInt();
+ r.comment = unescapeAndDecode( query.value( 1 ).toCString() );
+ r.rater = unescapeAndDecode( query.value( 2 ).toCString() );
+
+ command = QString( "SELECT rc.id,rc.name,rl.stars FROM rating_criteria rc, rating_criterion_list rl WHERE rating_id=%1 AND rl.rating_criterion_id=rc.id" ).arg(r.id);
+ QSqlQuery criterionQuery( command, database );
+ if ( criterionQuery.isActive() ) {
+ while ( criterionQuery.next() ) {
+ RatingCriteria rc;
+ rc.id = criterionQuery.value( 0 ).toInt();
+ rc.name = unescapeAndDecode( criterionQuery.value( 1 ).toCString() );
+ rc.stars = criterionQuery.value( 2 ).toDouble();
+ r.append( rc );
+ }
+ }
+
+ (*it).ratingList.append( r );
+ }
+ }
+ }
+ }
+
+ if ( items & RecipeDB::Properties ) {
+ for ( RecipeList::iterator recipe_it = rlist->begin(); recipe_it != rlist->end(); ++recipe_it ) {
+ RecipeList::iterator it = recipeIterators[ (*recipe_it).recipeID ];
+ calculateProperties( *it, this );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadIngredientGroups( ElementList *list )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM ingredient_groups ORDER BY name;";
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element group;
+ group.id = m_query.value( 0 ).toInt();
+ group.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( group );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadIngredients( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM ingredients ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element ing;
+ ing.id = m_query.value( 0 ).toInt();
+ ing.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( ing );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadPrepMethods( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM prep_methods ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element prep_method;
+ prep_method.id = m_query.value( 0 ).toInt();
+ prep_method.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( prep_method );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadYieldTypes( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM yield_types ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ m_query.exec( command );
+
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element el;
+ el.id = m_query.value( 0 ).toInt();
+ el.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::createNewPrepMethod( const QString &prepMethodName )
+{
+ QString command;
+ QString real_name = prepMethodName.left( maxPrepMethodNameLength() );
+
+ command = QString( "INSERT INTO prep_methods VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "prep_methods", "id" ) );
+ QSqlQuery prepMethodToCreate( command, database );
+
+ emit prepMethodCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::modPrepMethod( int prepMethodID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE prep_methods SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( prepMethodID );
+ QSqlQuery prepMethodToCreate( command, database );
+
+ emit prepMethodRemoved( prepMethodID );
+ emit prepMethodCreated( Element( newLabel, prepMethodID ) );
+}
+
+void QSqlRecipeDB::modProperty( int propertyID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE ingredient_properties SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( propertyID );
+ QSqlQuery createQuery( command, database );
+
+ emit propertyRemoved( propertyID );
+ emit propertyCreated( propertyName( propertyID ) );
+}
+
+void QSqlRecipeDB::loadPossibleUnits( int ingredientID, UnitList *list )
+{
+ list->clear();
+
+ QString command;
+
+ command = QString( "SELECT u.id,u.name,u.plural,u.name_abbrev,u.plural_abbrev,u.type FROM unit_list ul, units u WHERE ul.ingredient_id=%1 AND ul.unit_id=u.id;" ).arg( ingredientID );
+
+ QSqlQuery unitToLoad( command, database );
+
+ if ( unitToLoad.isActive() ) {
+ while ( unitToLoad.next() ) {
+ Unit unit;
+ unit.id = unitToLoad.value( 0 ).toInt();
+ unit.name = unescapeAndDecode( unitToLoad.value( 1 ).toCString() );
+ unit.plural = unescapeAndDecode( unitToLoad.value( 2 ).toCString() );
+ unit.name_abbrev = unescapeAndDecode( unitToLoad.value( 3 ).toCString() );
+ unit.plural_abbrev = unescapeAndDecode( unitToLoad.value( 4 ).toCString() );
+ unit.type = (Unit::Type) unitToLoad.value( 5 ).toInt();
+
+ list->append( unit );
+ }
+ }
+
+
+}
+
+void QSqlRecipeDB::storePhoto( int recipeID, const QByteArray &data )
+{
+ QSqlQuery query( QString::null, database );
+
+ query.prepare( "UPDATE recipes SET photo=?,ctime=ctime,atime=atime,mtime=mtime WHERE id=" + QString::number( recipeID ) );
+ query.addBindValue( KCodecs::base64Encode( data ) );
+ query.exec();
+}
+
+void QSqlRecipeDB::loadPhoto( int recipeID, QPixmap &photo )
+{
+ QString command = QString( "SELECT photo FROM recipes WHERE id=%1;" ).arg( recipeID );
+ QSqlQuery query( command, database );
+
+ if ( query.isActive() && query.first() ) {
+ QCString decodedPic;
+ QPixmap pix;
+ KCodecs::base64Decode( query.value( 0 ).toCString(), decodedPic );
+ int len = decodedPic.size();
+
+ if ( len > 0 ) {
+ QByteArray picData( len );
+ memcpy( picData.data(), decodedPic.data(), len );
+
+ bool ok = pix.loadFromData( picData, "JPEG" );
+ if ( ok )
+ photo = pix;
+ }
+ }
+}
+
+void QSqlRecipeDB::loadRecipeMetadata( Recipe *recipe )
+{
+ QString command = "SELECT ctime,mtime,atime FROM recipes WHERE id="+QString::number(recipe->recipeID);
+
+ QSqlQuery query( command, database );
+ if ( query.isActive() && query.first() ) {
+ recipe->ctime = query.value(0).toDateTime();
+ recipe->mtime = query.value(1).toDateTime();
+ recipe->atime = query.value(2).toDateTime();
+ }
+}
+
+void QSqlRecipeDB::saveRecipe( Recipe *recipe )
+{
+ // Check if it's a new recipe or it exists (supossedly) already.
+
+ bool newRecipe;
+ newRecipe = ( recipe->recipeID == -1 );
+ // First check if the recipe ID is set, if so, update (not create)
+ // Be carefull, first check if the recipe hasn't been deleted while changing.
+
+ QSqlQuery recipeToSave( QString::null, database );
+
+ QString command;
+
+ QDateTime current_datetime = QDateTime::currentDateTime();
+ QString current_timestamp = current_datetime.toString(Qt::ISODate);
+ if ( newRecipe ) {
+ command = QString( "INSERT INTO recipes VALUES ("+getNextInsertIDStr("recipes","id")+",'%1',%2,'%3','%4','%5',NULL,'%6','%7','%8','%9');" ) // Id is autoincremented
+ .arg( escapeAndEncode( recipe->title ) )
+ .arg( recipe->yield.amount )
+ .arg( recipe->yield.amount_offset )
+ .arg( recipe->yield.type_id )
+ .arg( escapeAndEncode( recipe->instructions ) )
+ .arg( recipe->prepTime.toString( "hh:mm:ss" ) )
+ .arg( current_timestamp )
+ .arg( current_timestamp )
+ .arg( current_timestamp );
+ recipe->mtime = recipe->ctime = recipe->atime = current_datetime;
+ }
+ else {
+ command = QString( "UPDATE recipes SET title='%1',yield_amount='%2',yield_amount_offset='%3',yield_type_id='%4',instructions='%5',prep_time='%6',mtime='%8',ctime=ctime,atime=atime WHERE id=%7;" )
+ .arg( escapeAndEncode( recipe->title ) )
+ .arg( recipe->yield.amount )
+ .arg( recipe->yield.amount_offset )
+ .arg( recipe->yield.type_id )
+ .arg( escapeAndEncode( recipe->instructions ) )
+ .arg( recipe->prepTime.toString( "hh:mm:ss" ) )
+ .arg( recipe->recipeID )
+ .arg( current_timestamp );
+ recipe->mtime = current_datetime;
+ }
+ recipeToSave.exec( command );
+
+ if ( !newRecipe ) {
+ // Clean up yield_types which have no recipe that they belong to
+ QStringList ids;
+ command = QString( "SELECT DISTINCT(yield_type_id) FROM recipes" );
+ recipeToSave.exec( command );
+ if ( recipeToSave.isActive() ) {
+ while ( recipeToSave.next() ) {
+ if ( recipeToSave.value( 0 ).toInt() != -1 )
+ ids << QString::number( recipeToSave.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM yield_types WHERE id NOT IN ( %1 )" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ recipeToSave.exec( command );
+ }
+
+ // If it's a new recipe, identify the ID that was given to the recipe and store in the Recipe itself
+ int recipeID;
+ if ( newRecipe ) {
+ recipeID = lastInsertID();
+ recipe->recipeID = recipeID;
+ }
+ recipeID = recipe->recipeID;
+ loadRecipeMetadata(recipe);
+
+ // Let's begin storing the Image!
+ if ( !recipe->photo.isNull() ) {
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ QImageIO iio( &buffer, "JPEG" );
+ iio.setImage( recipe->photo.convertToImage() );
+ iio.write();
+ //recipe->photo.save( &buffer, "JPEG" ); don't need QImageIO in QT 3.2
+
+ storePhoto( recipeID, ba );
+ }
+ else {
+ recipeToSave.exec( "UPDATE recipes SET photo=NULL, mtime=mtime, ctime=ctime, atime=atime WHERE id=" + QString::number( recipeID ) );
+ }
+
+ // Save the ingredient list (first delete if we are updating)
+ command = QString( "SELECT id FROM ingredient_list WHERE recipe_id=%1" ).arg(recipeID);
+ recipeToSave.exec( command );
+ if ( recipeToSave.isActive() ) {
+ while ( recipeToSave.next() ) {
+ command = QString("DELETE FROM prep_method_list WHERE ingredient_list_id=%1")
+ .arg(recipeToSave.value(0).toInt());
+ database->exec(command);
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id=%1;" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+ int order_index = 0;
+ for ( IngredientList::const_iterator ing_it = recipe->ingList.begin(); ing_it != recipe->ingList.end(); ++ing_it ) {
+ order_index++;
+ QString ing_list_id_str = getNextInsertIDStr("ingredient_list","id");
+ command = QString( "INSERT INTO ingredient_list VALUES (%1,%2,%3,%4,%5,%6,%7,%8,NULL);" )
+ .arg( ing_list_id_str )
+ .arg( recipeID )
+ .arg( ( *ing_it ).ingredientID )
+ .arg( ( *ing_it ).amount )
+ .arg( ( *ing_it ).amount_offset )
+ .arg( ( *ing_it ).units.id )
+ .arg( order_index )
+ .arg( ( *ing_it ).groupID );
+ recipeToSave.exec( command );
+
+ int ing_list_id = lastInsertID();
+ int prep_order_index = 0;
+ for ( ElementList::const_iterator prep_it = (*ing_it).prepMethodList.begin(); prep_it != (*ing_it).prepMethodList.end(); ++prep_it ) {
+ prep_order_index++;
+ command = QString( "INSERT INTO prep_method_list VALUES (%1,%2,%3);" )
+ .arg( ing_list_id )
+ .arg( ( *prep_it ).id )
+ .arg( prep_order_index );
+ recipeToSave.exec( command );
+ }
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ order_index++;
+ QString ing_list_id_str = getNextInsertIDStr("ingredient_list","id");
+ command = QString( "INSERT INTO ingredient_list VALUES (%1,%2,%3,%4,%5,%6,%7,%8,%9);" )
+ .arg( ing_list_id_str )
+ .arg( recipeID )
+ .arg( ( *sub_it ).ingredientID )
+ .arg( ( *sub_it ).amount )
+ .arg( ( *sub_it ).amount_offset )
+ .arg( ( *sub_it ).units.id )
+ .arg( order_index )
+ .arg( ( *sub_it ).groupID )
+ .arg( (*ing_it).ingredientID );
+ recipeToSave.exec( command );
+
+ int ing_list_id = lastInsertID();
+ int prep_order_index = 0;
+ for ( ElementList::const_iterator prep_it = (*sub_it).prepMethodList.begin(); prep_it != (*sub_it).prepMethodList.end(); ++prep_it ) {
+ prep_order_index++;
+ command = QString( "INSERT INTO prep_method_list VALUES (%1,%2,%3);" )
+ .arg( ing_list_id )
+ .arg( ( *prep_it ).id )
+ .arg( prep_order_index );
+ recipeToSave.exec( command );
+ }
+ }
+ }
+
+ // Save the category list for the recipe (first delete, in case we are updating)
+ command = QString( "DELETE FROM category_list WHERE recipe_id=%1;" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+ ElementList::const_iterator cat_it = recipe->categoryList.end(); // Start from last, mysql seems to work in lifo format... so it's read first the latest inserted one (newest)
+ --cat_it;
+ for ( unsigned int i = 0; i < recipe->categoryList.count(); i++ ) {
+ command = QString( "INSERT INTO category_list VALUES (%1,%2);" )
+ .arg( recipeID )
+ .arg( ( *cat_it ).id );
+ recipeToSave.exec( command );
+
+ --cat_it;
+ }
+
+ //Add the default category -1 to ease and speed up searches
+
+ command = QString( "INSERT INTO category_list VALUES (%1,-1);" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+
+ // Save the author list for the recipe (first delete, in case we are updating)
+ command = QString( "DELETE FROM author_list WHERE recipe_id=%1;" )
+ .arg( recipeID );
+ recipeToSave.exec( command );
+
+ ElementList::const_iterator author_it = recipe->authorList.end(); // Start from last, mysql seems to work in lifo format... so it's read first the latest inserted one (newest)
+ --author_it;
+ for ( unsigned int i = 0; i < recipe->authorList.count(); i++ ) {
+ command = QString( "INSERT INTO author_list VALUES (%1,%2);" )
+ .arg( recipeID )
+ .arg( ( *author_it ).id );
+ recipeToSave.exec( command );
+
+ --author_it;
+ }
+
+ // Save the ratings (first delete criterion list if we are updating)
+ command = QString( "SELECT id FROM ratings WHERE recipe_id=%1" ).arg(recipeID);
+ recipeToSave.exec( command );
+ if ( recipeToSave.isActive() ) {
+ while ( recipeToSave.next() ) {
+
+ command = QString("DELETE FROM rating_criterion_list WHERE rating_id=%1")
+ .arg(recipeToSave.value(0).toInt());
+ database->exec(command);
+ }
+ }
+
+ QStringList ids;
+
+ for ( RatingList::iterator rating_it = recipe->ratingList.begin(); rating_it != recipe->ratingList.end(); ++rating_it ) {
+ //double average = (*rating_it).average();
+ if ( (*rating_it).id == -1 ) //new rating
+ command ="INSERT INTO ratings VALUES("+QString(getNextInsertIDStr("ratings","id"))+","+QString::number(recipeID)+",'"+QString(escapeAndEncode((*rating_it).comment))+"','"+QString(escapeAndEncode((*rating_it).rater))+/*"','"+QString::number(average)+*/"','"+current_timestamp+"')";
+ else //existing rating
+ command = "UPDATE ratings SET "
+ "comment='"+QString(escapeAndEncode((*rating_it).comment))+"',"
+ "rater='"+QString(escapeAndEncode((*rating_it).rater))+"',"
+ "created=created "
+ "WHERE id="+QString::number((*rating_it).id);
+
+ recipeToSave.exec( command );
+
+ if ( (*rating_it).id == -1 )
+ (*rating_it).id = lastInsertID();
+
+ for ( QValueList<RatingCriteria>::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ command = QString( "INSERT INTO rating_criterion_list VALUES("+QString::number((*rating_it).id)+","+QString::number((*rc_it).id)+","+QString::number((*rc_it).stars)+")" );
+ recipeToSave.exec( command );
+ }
+
+ ids << QString::number((*rating_it).id);
+ }
+
+ // only delete those ratings that don't exist anymore
+ command = QString( "DELETE FROM ratings WHERE recipe_id=%1 AND id NOT IN( %2 )" )
+ .arg( recipeID ).arg( ids.join(",") );
+ recipeToSave.exec( command );
+
+ if ( newRecipe )
+ emit recipeCreated( Element( recipe->title.left( maxRecipeTitleLength() ), recipeID ), recipe->categoryList );
+ else
+ emit recipeModified( Element( recipe->title.left( maxRecipeTitleLength() ), recipeID ), recipe->categoryList );
+}
+
+void QSqlRecipeDB::loadRecipeList( ElementList *list, int categoryID, bool recursive )
+{
+ QString command;
+
+ if ( categoryID == -1 ) // load just the list
+ command = "SELECT id,title FROM recipes;";
+ else // load the list of those in the specified category
+ command = QString( "SELECT r.id,r.title FROM recipes r,category_list cl WHERE r.id=cl.recipe_id AND cl.category_id=%1 ORDER BY r.title" ).arg( categoryID );
+
+ if ( recursive ) {
+ QSqlQuery subcategories( QString("SELECT id FROM categories WHERE parent_id='%1'").arg(categoryID), database );
+ if ( subcategories.isActive() ) {
+ while ( subcategories.next() ) {
+ loadRecipeList(list,subcategories.value( 0 ).toInt(),true);
+ }
+ }
+ }
+
+ QSqlQuery recipeToLoad( command, database );
+
+ if ( recipeToLoad.isActive() ) {
+ while ( recipeToLoad.next() ) {
+ Element recipe;
+ recipe.id = recipeToLoad.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( recipeToLoad.value( 1 ).toCString() );
+ list->append( recipe );
+ }
+ }
+}
+
+
+void QSqlRecipeDB::loadUncategorizedRecipes( ElementList *list )
+{
+ list->clear();
+
+ QString command = "SELECT r.id,r.title FROM recipes r,category_list cl WHERE r.id=cl.recipe_id GROUP BY id HAVING COUNT(*)=1 ORDER BY r.title DESC";
+ m_query.exec( command );
+ if ( m_query.isActive() ) {
+ while ( m_query.next() ) {
+ Element recipe;
+ recipe.id = m_query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( m_query.value( 1 ).toCString() );
+ list->append( recipe );
+ }
+ }
+}
+
+
+
+void QSqlRecipeDB::removeRecipe( int id )
+{
+ emit recipeRemoved( id );
+
+ QString command;
+
+ command = QString( "DELETE FROM recipes WHERE id=%1;" ).arg( id );
+ QSqlQuery recipeToRemove( command, database );
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id=%1;" ).arg( id );
+ recipeToRemove.exec( command );
+ command = QString( "DELETE FROM category_list WHERE recipe_id=%1;" ).arg( id );
+ recipeToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ QStringList ids;
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ recipeToRemove.exec( command );
+ if ( recipeToRemove.isActive() ) {
+ while ( recipeToRemove.next() ) {
+ if ( recipeToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( recipeToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ recipeToRemove.exec( command );
+
+ // Clean up yield_types which have no recipe that they belong to
+ ids.clear();
+ command = QString( "SELECT DISTINCT(yield_type_id) FROM recipes" );
+ recipeToRemove.exec( command );
+ if ( recipeToRemove.isActive() ) {
+ while ( recipeToRemove.next() ) {
+ if ( recipeToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( recipeToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM yield_types WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ recipeToRemove.exec( command );
+}
+
+void QSqlRecipeDB::removeRecipeFromCategory( int recipeID, int categoryID )
+{
+ QString command;
+ command = QString( "DELETE FROM category_list WHERE recipe_id=%1 AND category_id=%2;" ).arg( recipeID ).arg( categoryID );
+ QSqlQuery recipeToRemove( command, database );
+
+ emit recipeRemoved( recipeID, categoryID );
+}
+
+void QSqlRecipeDB::categorizeRecipe( int recipeID, const ElementList &categoryList )
+{
+ QString command;
+
+ //emit recipeRemoved( recipeID, -1 );
+
+ for ( ElementList::const_iterator it = categoryList.begin(); it != categoryList.end(); ++it ) {
+ command = QString( "INSERT INTO category_list VALUES(%1,%2)" ).arg( recipeID ).arg( (*it).id );
+ database->exec( command );
+ }
+
+ emit recipeModified( Element(recipeTitle(recipeID),recipeID), categoryList );
+}
+
+void QSqlRecipeDB::createNewIngGroup( const QString &name )
+{
+ QString command;
+ QString real_name = name.left( maxIngGroupNameLength() );
+
+ command = QString( "INSERT INTO ingredient_groups VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "ingredient_groups", "id" ) );
+ QSqlQuery query( command, database );
+
+ emit ingGroupCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::createNewIngredient( const QString &ingredientName )
+{
+ QString command;
+ QString real_name = ingredientName.left( maxIngredientNameLength() );
+
+ command = QString( "INSERT INTO ingredients VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "ingredients", "id" ) );
+ QSqlQuery ingredientToCreate( command, database );
+
+ emit ingredientCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::createNewRating( const QString &rating )
+{
+ QString command;
+ QString real_name = rating/*.left( maxIngredientNameLength() )*/;
+
+ command = QString( "INSERT INTO rating_criteria VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "rating_criteria", "id" ) );
+ QSqlQuery toCreate( command, database );
+
+ emit ratingCriteriaCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::createNewYieldType( const QString &name )
+{
+ QString command;
+ QString real_name = name.left( maxYieldTypeLength() );
+
+ command = QString( "INSERT INTO yield_types VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "yield_types", "id" ) );
+ database->exec(command);
+
+ //emit yieldTypeCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::modIngredientGroup( int groupID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE ingredient_groups SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( groupID );
+ QSqlQuery ingredientToCreate( command, database );
+
+ emit ingGroupRemoved( groupID );
+ emit ingGroupCreated( Element( newLabel, groupID ) );
+}
+
+void QSqlRecipeDB::modIngredient( int ingredientID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE ingredients SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( ingredientID );
+ QSqlQuery ingredientToCreate( command, database );
+
+ emit ingredientRemoved( ingredientID );
+ emit ingredientCreated( Element( newLabel, ingredientID ) );
+}
+
+void QSqlRecipeDB::addUnitToIngredient( int ingredientID, int unitID )
+{
+ QString command;
+
+ command = QString( "INSERT INTO unit_list VALUES(%1,%2);" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery ingredientToCreate( command, database );
+}
+
+void QSqlRecipeDB::loadUnits( UnitList *list, Unit::Type type, int limit, int offset )
+{
+ list->clear();
+
+ QString command;
+
+ command = "SELECT id,name,name_abbrev,plural,plural_abbrev,type FROM units "
+ +((type==Unit::All)?"":"WHERE type="+QString::number(type))
+ +" ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+
+ QSqlQuery unitToLoad( command, database );
+
+ if ( unitToLoad.isActive() ) {
+ while ( unitToLoad.next() ) {
+ Unit unit;
+ unit.id = unitToLoad.value( 0 ).toInt();
+ unit.name = unescapeAndDecode( unitToLoad.value( 1 ).toCString() );
+ unit.name_abbrev = unescapeAndDecode( unitToLoad.value( 2 ).toCString() );
+ unit.plural = unescapeAndDecode( unitToLoad.value( 3 ).toCString() );
+ unit.plural_abbrev = unescapeAndDecode( unitToLoad.value( 4 ).toCString() );
+ unit.type = (Unit::Type)unitToLoad.value( 5 ).toInt();
+ list->append( unit );
+ }
+ }
+}
+
+void QSqlRecipeDB::removeUnitFromIngredient( int ingredientID, int unitID )
+{
+ QString command;
+
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+
+ // Remove any recipe using that combination of ingredients also (user must have been warned before calling this function!)
+
+ command = QString( "SELECT r.id FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1 AND il.unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ emit recipeRemoved( unitToRemove.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( unitToRemove.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Remove any ingredient in ingredient_list which has references to this unit and ingredient
+ command = QString( "DELETE FROM ingredient_list WHERE ingredient_id=%1 AND unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Remove any ingredient properties from ingredient_info where the this ingredient+unit is being used (user must have been warned before calling this function!)
+ command = QString( "DELETE FROM ingredient_info ii WHERE ii.ingredient_id=%1 AND ii.per_units=%2;" ).arg( ingredientID ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ if ( unitToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+}
+
+void QSqlRecipeDB::removeIngredientGroup( int groupID )
+{
+ QString command;
+
+ // First remove the ingredient
+
+ command = QString( "DELETE FROM ingredient_groups WHERE id=%1" ).arg( groupID );
+ QSqlQuery toDelete( command, database );
+
+ // Remove all the unit entries for this ingredient
+
+ command = QString( "UPDATE ingredient_list SET group_id='-1' WHERE group_id=%1" ).arg( groupID );
+ toDelete.exec( command );
+
+ emit ingGroupRemoved( groupID );
+}
+
+void QSqlRecipeDB::removeIngredient( int ingredientID )
+{
+ QString command;
+
+ // First remove the ingredient
+
+ command = QString( "DELETE FROM ingredients WHERE id=%1;" ).arg( ingredientID );
+ QSqlQuery ingredientToDelete( command, database );
+
+ // Remove all the unit entries for this ingredient
+
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+
+ // Remove any recipe using that ingredient
+
+ command = QString( "SELECT r.id FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+ if ( ingredientToDelete.isActive() ) {
+ while ( ingredientToDelete.next() ) {
+ emit recipeRemoved( ingredientToDelete.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( ingredientToDelete.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Remove any ingredient in ingredient_list which has references to this ingredient
+ command = QString( "DELETE FROM ingredient_list WHERE ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ ingredientToDelete.exec( command );
+ if ( ingredientToDelete.isActive() ) {
+ while ( ingredientToDelete.next() ) {
+ ids << QString::number( ingredientToDelete.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ ingredientToDelete.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to. Same method as above
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ ingredientToDelete.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ ingredientToDelete.exec( command );
+ if ( ingredientToDelete.isActive() ) {
+ while ( ingredientToDelete.next() ) {
+ if ( ingredientToDelete.value( 0 ).toInt() != -1 )
+ ids << QString::number( ingredientToDelete.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ ingredientToDelete.exec( command );
+
+ // Remove property list of this ingredient
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1;" ).arg( ingredientID );
+ ingredientToDelete.exec( command );
+
+ emit ingredientRemoved( ingredientID );
+}
+
+void QSqlRecipeDB::removeIngredientWeight( int id )
+{
+ QString command;
+
+ // First remove the ingredient
+
+ command = QString( "DELETE FROM ingredient_weights WHERE id=%1" ).arg( id );
+ QSqlQuery toDelete( command, database );
+}
+
+void QSqlRecipeDB::addIngredientWeight( const Weight &w )
+{
+ QString command;
+ if ( w.id != -1 ) {
+ command = QString( "UPDATE ingredient_weights SET ingredient_id=%1,amount=%2,unit_id=%3,weight=%4,weight_unit_id=%5,prep_method_id=%7 WHERE id=%6" )
+ .arg(w.ingredientID)
+ .arg(w.perAmount)
+ .arg(w.perAmountUnitID)
+ .arg(w.weight)
+ .arg(w.weightUnitID)
+ .arg(w.id)
+ .arg(w.prepMethodID);
+ }
+ else {
+ command = QString( "INSERT INTO ingredient_weights VALUES(%6,%1,%2,%3,%4,%5,%7)" )
+ .arg(w.ingredientID)
+ .arg(w.perAmount)
+ .arg(w.perAmountUnitID)
+ .arg(w.weight)
+ .arg(w.weightUnitID)
+ .arg(getNextInsertIDStr( "ingredient_weights", "id" ))
+ .arg(w.prepMethodID);
+ }
+ QSqlQuery query( command, database );
+}
+
+void QSqlRecipeDB::addProperty( const QString &name, const QString &units )
+{
+ QString command;
+ QString real_name = name.left( maxPropertyNameLength() );
+
+ command = QString( "INSERT INTO ingredient_properties VALUES(%3,'%1','%2');" )
+ .arg( escapeAndEncode( real_name ) )
+ .arg( escapeAndEncode( units ) )
+ .arg( getNextInsertIDStr( "ingredient_properties", "id" ) );
+ QSqlQuery propertyToAdd( command, database );
+
+ emit propertyCreated( IngredientProperty( real_name, units, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::loadProperties( IngredientPropertyList *list, int ingredientID )
+{
+ list->clear();
+ QString command;
+ bool usePerUnit;
+ if ( ingredientID >= 0 ) // Load properties of this ingredient
+ {
+ usePerUnit = true;
+ command = QString( "SELECT ip.id,ip.name,ip.units,ii.per_units,u.name,u.type,ii.amount,ii.ingredient_id FROM ingredient_properties ip, ingredient_info ii, units u WHERE ii.ingredient_id=%1 AND ii.property_id=ip.id AND ii.per_units=u.id;" ).arg( ingredientID );
+ }
+ else if ( ingredientID == -1 ) // Load the properties of all the ingredients
+ {
+ usePerUnit = true;
+ command = QString( "SELECT ip.id,ip.name,ip.units,ii.per_units,u.name,u.type,ii.amount,ii.ingredient_id FROM ingredient_properties ip, ingredient_info ii, units u WHERE ii.property_id=ip.id AND ii.per_units=u.id;" );
+ }
+ else // Load the whole property list (just the list of possible properties, not the ingredient properties)
+ {
+ usePerUnit = false;
+ command = QString( "SELECT id,name,units FROM ingredient_properties;" );
+ }
+
+ QSqlQuery propertiesToLoad ( command, database );
+ // Load the results into the list
+ if ( propertiesToLoad.isActive() ) {
+ while ( propertiesToLoad.next() ) {
+ IngredientProperty prop;
+ prop.id = propertiesToLoad.value( 0 ).toInt();
+ prop.name = unescapeAndDecode( propertiesToLoad.value( 1 ).toCString() );
+ prop.units = unescapeAndDecode( propertiesToLoad.value( 2 ).toCString() );
+ if ( usePerUnit ) {
+ prop.perUnit.id = propertiesToLoad.value( 3 ).toInt();
+ prop.perUnit.name = unescapeAndDecode( propertiesToLoad.value( 4 ).toCString() );
+ prop.perUnit.type = (Unit::Type)propertiesToLoad.value( 5 ).toInt();
+ }
+
+ if ( ingredientID >= -1 )
+ prop.amount = propertiesToLoad.value( 6 ).toDouble();
+ else
+ prop.amount = -1; // Property is generic, not attached to an ingredient
+
+ if ( ingredientID >= -1 )
+ prop.ingredientID = propertiesToLoad.value( 7 ).toInt();
+
+ list->append( prop );
+ }
+ }
+}
+
+void QSqlRecipeDB::changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units )
+{
+ QString command;
+ command = QString( "UPDATE ingredient_info SET amount=%1 WHERE ingredient_id=%2 AND property_id=%3 AND per_units=%4;" ).arg( amount ).arg( ingredientID ).arg( propertyID ).arg( per_units );
+ QSqlQuery infoToChange( command, database );
+}
+
+void QSqlRecipeDB::addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID )
+{
+ QString command;
+
+ command = QString( "INSERT INTO ingredient_info VALUES(%1,%2,%3,%4);" ).arg( ingredientID ).arg( propertyID ).arg( amount ).arg( perUnitsID );
+ QSqlQuery propertyToAdd( command, database );
+}
+
+
+void QSqlRecipeDB::removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID )
+{
+ QString command;
+ // remove property from ingredient info. Note that there could be duplicates with different units (per_units). Remove just the one especified.
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2 AND per_units=%3;" ).arg( ingredientID ).arg( propertyID ).arg( perUnitID );
+ QSqlQuery propertyToRemove( command, database );
+}
+
+void QSqlRecipeDB::removeProperty( int propertyID )
+{
+ QString command;
+
+ // Remove property from the ingredient_properties
+ command = QString( "DELETE FROM ingredient_properties WHERE id=%1;" ).arg( propertyID );
+ QSqlQuery propertyToRemove( command, database );
+
+ // Remove any ingredient info that uses this property
+ command = QString( "DELETE FROM ingredient_info WHERE property_id=%1;" ).arg( propertyID );
+ propertyToRemove.exec( command );
+
+ emit propertyRemoved( propertyID );
+}
+
+void QSqlRecipeDB::removeUnit( int unitID )
+{
+ QString command;
+ // Remove the unit first
+ command = QString( "DELETE FROM units WHERE id=%1;" ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+
+ //Remove the unit from ingredients using it
+
+ command = QString( "DELETE FROM unit_list WHERE unit_id=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+
+
+ // Remove any recipe using that unit in the ingredient list (user must have been warned before calling this function!)
+
+ command = QString( "SELECT r.id FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.unit_id=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ emit recipeRemoved( unitToRemove.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( unitToRemove.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Remove any ingredient in ingredient_list which has references to this unit
+ command = QString( "DELETE FROM ingredient_list WHERE unit_id=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to. Same method as above
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ if ( unitToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( unitToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ unitToRemove.exec( command );
+
+ // Remove the ingredient properties using this unit (user must be warned before calling this function)
+ command = QString( "DELETE FROM ingredient_info WHERE per_units=%1;" ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Remove the unit conversion ratios with this unit
+ command = QString( "DELETE FROM units_conversion WHERE unit1_id=%1 OR unit2_id=%2;" ).arg( unitID ).arg( unitID );
+ unitToRemove.exec( command );
+
+ // Remove associated ingredient weights
+ command = QString( "DELETE FROM ingredient_weights WHERE unit_id=%1" ).arg( unitID );
+ unitToRemove.exec( command );
+
+ emit unitRemoved( unitID );
+}
+
+void QSqlRecipeDB::removePrepMethod( int prepMethodID )
+{
+ QString command;
+ // Remove the prep method first
+ command = QString( "DELETE FROM prep_methods WHERE id=%1;" ).arg( prepMethodID );
+ QSqlQuery prepMethodToRemove( command, database );
+
+ // Remove any recipe using that prep method in the ingredient list (user must have been warned before calling this function!)
+
+ command = QString( "SELECT DISTINCT r.id FROM recipes r,ingredient_list il, prep_method_list pl WHERE r.id=il.recipe_id AND pl.ingredient_list_id=il.id AND pl.prep_method_id=%1;" ).arg( prepMethodID );
+ prepMethodToRemove.exec( command );
+ if ( prepMethodToRemove.isActive() ) {
+ while ( prepMethodToRemove.next() ) {
+ emit recipeRemoved( prepMethodToRemove.value( 0 ).toInt() );
+ database->exec( QString( "DELETE FROM recipes WHERE id=%1;" ).arg( prepMethodToRemove.value( 0 ).toInt() ) );
+ }
+ }
+
+ // Clean up ingredient_list which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_list WHERE recipe_id NOT IN ( SELECT id FROM recipes );)
+ QStringList ids;
+ command = QString( "SELECT id FROM recipes;" );
+ prepMethodToRemove.exec( command );
+ if ( prepMethodToRemove.isActive() ) {
+ while ( prepMethodToRemove.next() ) {
+ ids << QString::number( prepMethodToRemove.value( 0 ).toInt() );
+ }
+ }
+
+ command = QString( "DELETE FROM ingredient_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ prepMethodToRemove.exec( command );
+
+ // Clean up category_list which have no recipe that they belong to. Same method as above
+ command = QString( "DELETE FROM category_list WHERE recipe_id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ prepMethodToRemove.exec( command );
+
+ // Clean up ingredient_groups which have no recipe that they belong to
+ // MySQL doesn't support subqueries until 4.1, so we'll do this the long way
+ // (Easy way: DELETE FROM ingredient_groups WHERE id NOT IN ( SELECT DISTINCT(group_id) FROM ingredient_list );)
+ ids.clear();
+ command = QString( "SELECT DISTINCT(group_id) FROM ingredient_list;" );
+ prepMethodToRemove.exec( command );
+ if ( prepMethodToRemove.isActive() ) {
+ while ( prepMethodToRemove.next() ) {
+ if ( prepMethodToRemove.value( 0 ).toInt() != -1 )
+ ids << QString::number( prepMethodToRemove.value( 0 ).toInt() );
+ }
+ }
+ command = QString( "DELETE FROM ingredient_groups WHERE id NOT IN ( %1 );" ).arg( ( ids.count() == 0 ) ? "-1" : ids.join( "," ) );
+ prepMethodToRemove.exec( command );
+
+ emit prepMethodRemoved( prepMethodID );
+}
+
+
+void QSqlRecipeDB::createNewUnit( const Unit &unit )
+{
+ QString real_name = unit.name.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural = unit.plural.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_name_abbrev = unit.name_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural_abbrev = unit.plural_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+
+ Unit new_unit( real_name, real_plural );
+ new_unit.name_abbrev = real_name_abbrev;
+ new_unit.plural_abbrev = real_plural_abbrev;
+ new_unit.type = unit.type;
+
+ if ( real_name.isEmpty() )
+ real_name = real_plural;
+ else if ( real_plural.isEmpty() )
+ real_plural = real_name;
+
+ if ( real_name_abbrev.isEmpty() )
+ real_name_abbrev = "NULL";
+ else
+ real_name_abbrev = "'"+escapeAndEncode(real_name_abbrev)+"'";
+ if ( real_plural_abbrev.isEmpty() )
+ real_plural_abbrev = "NULL";
+ else
+ real_plural_abbrev = "'"+escapeAndEncode(real_plural_abbrev)+"'";
+
+
+ QString command = "INSERT INTO units VALUES(" + getNextInsertIDStr( "units", "id" )
+ + ",'" + escapeAndEncode( real_name )
+ + "'," + real_name_abbrev
+ + ",'" + escapeAndEncode( real_plural )
+ + "'," + real_plural_abbrev
+ + "," + QString::number(unit.type)
+ + ");";
+
+ QSqlQuery unitToCreate( command, database );
+
+ new_unit.id = lastInsertID();
+ emit unitCreated( new_unit );
+}
+
+
+void QSqlRecipeDB::modUnit( const Unit &unit )
+{
+ QSqlQuery unitQuery( QString::null, database );
+
+ QString real_name = unit.name.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural = unit.plural.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_name_abbrev = unit.name_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+ QString real_plural_abbrev = unit.plural_abbrev.left( maxUnitNameLength() ).stripWhiteSpace();
+
+ Unit newUnit( real_name, real_plural, unit.id );
+ newUnit.type = unit.type;
+ newUnit.name_abbrev = real_name_abbrev;
+ newUnit.plural_abbrev = real_plural_abbrev;
+
+ if ( real_name_abbrev.isEmpty() )
+ real_name_abbrev = "NULL";
+ else
+ real_name_abbrev = "'"+escapeAndEncode(real_name_abbrev)+"'";
+ if ( real_plural_abbrev.isEmpty() )
+ real_plural_abbrev = "NULL";
+ else
+ real_plural_abbrev = "'"+escapeAndEncode(real_plural_abbrev)+"'";
+
+ QString command = QString("UPDATE units SET name='%1',name_abbrev=%2,plural='%3',plural_abbrev=%4,type=%6 WHERE id='%5'")
+ .arg(escapeAndEncode(real_name))
+ .arg(real_name_abbrev)
+ .arg(escapeAndEncode(real_plural))
+ .arg(real_plural_abbrev)
+ .arg(unit.id)
+ .arg(unit.type);
+ unitQuery.exec( command );
+
+ emit unitRemoved( unit.id );
+ emit unitCreated( newUnit );
+}
+
+void QSqlRecipeDB::findUseOfIngGroupInRecipes( ElementList *results, int groupID )
+{
+ QString command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.group_id=%1" ).arg( groupID );
+ QSqlQuery query( command, database );
+
+ // Populate data
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Element recipe;
+ recipe.id = query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( query.value( 1 ).toCString() );
+ results->append( recipe );
+ }
+ }
+}
+
+void QSqlRecipeDB::findUseOfCategoryInRecipes( ElementList *results, int catID )
+{
+ QString command = QString( "SELECT r.id,r.title FROM recipes r,category_list cl WHERE r.id=cl.recipe_id AND cl.category_id=%1" ).arg( catID );
+ QSqlQuery query( command, database );
+
+ // Populate data
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Element recipe;
+ recipe.id = query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( query.value( 1 ).toCString() );
+ results->append( recipe );
+ }
+ }
+
+ //recursively find dependenacies in subcategories
+ command = QString( "SELECT id FROM categories WHERE parent_id=%1" ).arg( catID );
+ QSqlQuery findDeps = database->exec( command );
+ if ( findDeps.isActive() ) {
+ while ( findDeps.next() ) {
+ findUseOfCategoryInRecipes(results,findDeps.value( 0 ).toInt() );
+ }
+ }
+}
+
+void QSqlRecipeDB::findUseOfAuthorInRecipes( ElementList *results, int authorID )
+{
+ QString command = QString( "SELECT r.id,r.title FROM recipes r,author_list al WHERE r.id=al.recipe_id AND al.author_id=%1" ).arg( authorID );
+ QSqlQuery query( command, database );
+
+ // Populate data
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Element recipe;
+ recipe.id = query.value( 0 ).toInt();
+ recipe.name = unescapeAndDecode( query.value( 1 ).toCString() );
+ results->append( recipe );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadUnitRatios( UnitRatioList *ratioList, Unit::Type type )
+{
+ ratioList->clear();
+
+ QString command;
+ if ( type == Unit::All )
+ command = "SELECT unit1_id,unit2_id,ratio FROM units_conversion";
+ else
+ command = "SELECT unit1_id,unit2_id,ratio FROM units_conversion,units unit1,units unit2 WHERE unit1_id=unit1.id AND unit1.type="+QString::number(type)+" AND unit2_id=unit2.id AND unit2.type="+QString::number(type);
+ QSqlQuery ratiosToLoad( command, database );
+
+ if ( ratiosToLoad.isActive() ) {
+ while ( ratiosToLoad.next() ) {
+ UnitRatio ratio;
+ ratio.uID1 = ratiosToLoad.value( 0 ).toInt();
+ ratio.uID2 = ratiosToLoad.value( 1 ).toInt();
+ ratio.ratio = ratiosToLoad.value( 2 ).toDouble();
+ ratioList->add( ratio );
+ }
+ }
+}
+
+void QSqlRecipeDB::saveUnitRatio( const UnitRatio *ratio )
+{
+ QString command;
+
+ // Check if it's a new ratio or it exists already.
+ command = QString( "SELECT * FROM units_conversion WHERE unit1_id=%1 AND unit2_id=%2" ).arg( ratio->uID1 ).arg( ratio->uID2 ); // Find ratio between units
+
+ QSqlQuery ratioFound( command, database ); // Find the entries
+ bool newRatio = ( ratioFound.size() == 0 );
+
+ if ( newRatio )
+ command = QString( "INSERT INTO units_conversion VALUES(%1,%2,%3);" ).arg( ratio->uID1 ).arg( ratio->uID2 ).arg( ratio->ratio );
+ else
+ command = QString( "UPDATE units_conversion SET ratio=%3 WHERE unit1_id=%1 AND unit2_id=%2" ).arg( ratio->uID1 ).arg( ratio->uID2 ).arg( ratio->ratio );
+
+ ratioFound.exec( command ); // Enter the new ratio
+}
+
+void QSqlRecipeDB::removeUnitRatio( int unitID1, int unitID2 )
+{
+ database->exec(QString( "DELETE FROM units_conversion WHERE unit1_id=%1 AND unit2_id=%2" ).arg( unitID1 ).arg( unitID2 ));
+}
+
+double QSqlRecipeDB::unitRatio( int unitID1, int unitID2 )
+{
+
+ if ( unitID1 == unitID2 )
+ return ( 1.0 );
+ QString command;
+
+ command = QString( "SELECT ratio FROM units_conversion WHERE unit1_id=%1 AND unit2_id=%2;" ).arg( unitID1 ).arg( unitID2 );
+ QSqlQuery ratioToLoad( command, database );
+
+ if ( ratioToLoad.isActive() && ratioToLoad.next() )
+ return ( ratioToLoad.value( 0 ).toDouble() );
+ else
+ return ( -1 );
+}
+
+double QSqlRecipeDB::ingredientWeight( const Ingredient &ing, bool *wasApproximated )
+{
+ QString command = QString( "SELECT amount,weight,prep_method_id,unit_id FROM ingredient_weights WHERE ingredient_id=%1 AND (unit_id=%2 OR weight_unit_id=%3)" )
+ .arg( ing.ingredientID )
+ .arg( ing.units.id ).arg( ing.units.id );
+
+ QSqlQuery query( command, database );
+
+ if ( query.isActive() ) {
+ //store the amount for the entry with no prep method. If no other suitable entry is found, we'll guesstimate
+ //the weight using this entry
+ double convertedAmount = -1;
+ while ( query.next() ) {
+ int prepMethodID = query.value( 2 ).toInt();
+
+ if ( ing.prepMethodList.containsId( prepMethodID ) ) {
+ if ( wasApproximated ) *wasApproximated = false;
+ double amount = query.value( 0 ).toDouble();
+
+ //'per_amount' -> 'weight' conversion
+ if ( query.value( 3 ).toInt() == ing.units.id )
+ convertedAmount = query.value( 1 ).toDouble() * ing.amount / amount;
+ //'weight' -> 'per_amount' conversion
+ else
+ convertedAmount = amount * ing.amount / query.value( 1 ).toDouble();
+
+ return convertedAmount;
+ }
+ if ( prepMethodID == -1 ) {
+ //'per_amount' -> 'weight' conversion
+ if ( query.value( 3 ).toInt() == ing.units.id )
+ convertedAmount = query.value( 1 ).toDouble() * ing.amount / query.value( 0 ).toDouble();
+ //'weight' -> 'per_amount' conversion
+ else
+ convertedAmount = query.value( 0 ).toDouble() * ing.amount / query.value( 1 ).toDouble();
+ }
+ }
+ //no matching prep method found, use entry without a prep method if there was one
+ if ( convertedAmount > 0 ) {
+ if ( wasApproximated ) *wasApproximated = true;
+ kdDebug()<<"Prep method given, but no weight entry found that uses that prep method. I'm fudging the weight with an entry without a prep method."<<endl;
+
+ return convertedAmount;
+ }
+ }
+ return -1;
+}
+
+WeightList QSqlRecipeDB::ingredientWeightUnits( int ingID )
+{
+ WeightList list;
+
+ QString command = QString( "SELECT id,amount,unit_id,weight,weight_unit_id,prep_method_id FROM ingredient_weights WHERE ingredient_id=%1" ).arg( ingID );
+ QSqlQuery query( command, database );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ Weight w;
+ w.id = query.value(0).toInt();
+ w.perAmount = query.value(1).toDouble();
+ w.perAmountUnitID = query.value(2).toInt();
+ w.weight = query.value(3).toDouble();
+ w.weightUnitID = query.value(4).toInt();
+ w.prepMethodID = query.value(5).toInt();
+ w.ingredientID = ingID;
+ list.append(w);
+ }
+ }
+
+ return list;
+}
+
+//Finds data dependant on this Ingredient/Unit combination
+void QSqlRecipeDB::findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo )
+{
+
+ // Recipes using that combination
+
+ QString command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1 AND il.unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+ loadElementList( recipes, &unitToRemove );
+ // Ingredient info using that combination
+ command = QString( "SELECT i.name,ip.name,ip.units,u.name FROM ingredients i, ingredient_info ii, ingredient_properties ip, units u WHERE i.id=ii.ingredient_id AND ii.ingredient_id=%1 AND ii.per_units=%2 AND ii.property_id=ip.id AND ii.per_units=u.id;" ).arg( ingredientID ).arg( unitID );
+
+ unitToRemove.exec( command );
+ loadPropertyElementList( ingredientInfo, &unitToRemove );
+}
+
+void QSqlRecipeDB::findIngredientDependancies( int ingredientID, ElementList *recipes )
+{
+ QString command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.ingredient_id=%1" ).arg( ingredientID );
+
+ QSqlQuery ingredientToRemove( command, database );
+ loadElementList( recipes, &ingredientToRemove );
+}
+
+
+
+//Finds data dependant on the removal of this Unit
+void QSqlRecipeDB::findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights )
+{
+
+ // Ingredient-Info (ingredient->property) using this Unit
+
+ QString command = QString( "SELECT i.name,ip.name,ip.units,u.name FROM ingredients i, ingredient_info ii, ingredient_properties ip, units u WHERE i.id=ii.ingredient_id AND ii.per_units=%1 AND ii.property_id=ip.id AND ii.per_units=u.id;" ).arg( unitID );
+ QSqlQuery unitToRemove( command, database );
+ loadPropertyElementList( properties, &unitToRemove );
+
+ // Recipes using this Unit
+ command = QString( "SELECT DISTINCT r.id,r.title FROM recipes r,ingredient_list il WHERE r.id=il.recipe_id AND il.unit_id=%1;" ).arg( unitID ); // Without "DISTINCT" we get duplicates since ingredient_list has no unique recipe_id's
+ unitToRemove.exec( command );
+ loadElementList( recipes, &unitToRemove );
+
+ // Weights using this unit
+ command = QString( "SELECT i.name,weight_u.name,per_u.name,w.prep_method_id FROM ingredients i,ingredient_weights w,units weight_u,units per_u WHERE i.id=w.ingredient_id AND w.unit_id=per_u.id AND w.weight_unit_id=weight_u.id AND (weight_u.id=%1 OR per_u.id=%2)" )
+ .arg( unitID )
+ .arg( unitID );
+ unitToRemove.exec( command );
+ if ( unitToRemove.isActive() ) {
+ while ( unitToRemove.next() ) {
+ Element el;
+
+ QString ingName = unescapeAndDecode( unitToRemove.value( 0 ).toCString() );
+ QString weightUnit = unescapeAndDecode( unitToRemove.value( 1 ).toCString() );
+ QString perUnit = unescapeAndDecode( unitToRemove.value( 2 ).toCString() );
+
+ int prepID = unitToRemove.value( 3 ).toInt();
+ QString prep;
+ if ( prepID != -1 ) {
+ command = QString( "SELECT p.name FROM prep_methods p, ingredient_weights w WHERE p.id = w.prep_method_id AND w.prep_method_id=%1" )
+ .arg( prepID );
+ QSqlQuery query( command, database );
+ if ( query.isActive() && query.first() )
+ prep = unescapeAndDecode( query.value( 0 ).toCString() );
+ }
+
+ el.name = QString( i18n("In ingredient '%1': weight [%2/%3%4]") ).arg( ingName ).arg( weightUnit ).arg( perUnit ).arg( (prepID == -1)?QString::null:"; "+prep );
+ weights->append( el );
+ }
+ }
+
+}
+
+void QSqlRecipeDB::findPrepMethodDependancies( int prepMethodID, ElementList *recipes )
+{
+ //get just the ids first so that we can use DISTINCT
+ QString command = QString( "SELECT DISTINCT r.id FROM recipes r,ingredient_list il, prep_method_list pl WHERE r.id=il.recipe_id AND pl.ingredient_list_id=il.id AND pl.prep_method_id=%1;" ).arg( prepMethodID );
+
+ QStringList ids;
+ QSqlQuery query( command, database );
+ if ( query.isActive() ) {
+ while ( query.next() ) {
+ ids << QString::number(query.value( 0 ).toInt());
+ }
+ }
+
+ //now get the titles of the ids
+ command = QString( "SELECT r.id, r.title FROM recipes r WHERE r.id IN ("+ids.join(",")+")" );
+ QSqlQuery prepMethodToRemove( command, database );
+ loadElementList( recipes, &prepMethodToRemove );
+}
+
+
+void QSqlRecipeDB::loadElementList( ElementList *elList, QSqlQuery *query )
+{
+ if ( query->isActive() ) {
+ while ( query->next() ) {
+ Element el;
+ el.id = query->value( 0 ).toInt();
+ el.name = unescapeAndDecode( query->value( 1 ).toCString() );
+ elList->append( el );
+ }
+ }
+}
+// See function "findUnitDependancies" for use
+void QSqlRecipeDB::loadPropertyElementList( ElementList *elList, QSqlQuery *query )
+{
+ if ( query->isActive() ) {
+ while ( query->next() ) {
+ Element el;
+ el.id = -1; // There's no ID for the ingredient-property combination
+ QString ingName = unescapeAndDecode( query->value( 0 ).toCString() );
+ QString propName = unescapeAndDecode( query->value( 1 ).toCString() );
+ QString propUnits = unescapeAndDecode( query->value( 2 ).toCString() );
+ QString propPerUnits = unescapeAndDecode( query->value( 3 ).toCString() );
+
+ el.name = QString( i18n("In ingredient '%1': property \"%2\" [%3/%4]") ).arg( ingName ).arg( propName ).arg( propUnits ).arg( propPerUnits );
+ elList->append( el );
+ }
+ }
+}
+
+
+//The string going into the database is utf8 text interpreted as latin1
+QString QSqlRecipeDB::escapeAndEncode( const QString &s ) const
+{
+ QString s_escaped = s;
+
+ s_escaped.replace ( "'", "\\'" );
+ s_escaped.replace ( ";", "\";@" ); // Small trick for only for parsing later on
+
+ return QString::fromLatin1( s_escaped.utf8() );
+}
+
+//The string coming out of the database is utf8 text, interpreted as though latin1. Calling fromUtf8() on this gives us back the original utf8.
+QString QSqlRecipeDB::unescapeAndDecode( const QCString &s ) const
+{
+ return QString::fromUtf8( s ).replace( "\";@", ";" ); // Use unicode encoding
+}
+
+bool QSqlRecipeDB::ingredientContainsUnit( int ingredientID, int unitID )
+{
+ QString command = QString( "SELECT * FROM unit_list WHERE ingredient_id= %1 AND unit_id=%2;" ).arg( ingredientID ).arg( unitID );
+ QSqlQuery recipeToLoad( command, database );
+ if ( recipeToLoad.isActive() ) {
+ return ( recipeToLoad.size() > 0 );
+ }
+ return false;
+}
+
+bool QSqlRecipeDB::ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID )
+{
+ QString command = QString( "SELECT * FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2 AND per_units=%3;" ).arg( ingredientID ).arg( propertyID ).arg( perUnitsID );
+ QSqlQuery recipeToLoad( command, database );
+ if ( recipeToLoad.isActive() ) {
+ return ( recipeToLoad.size() > 0 );
+ }
+ return false;
+}
+
+QString QSqlRecipeDB::categoryName( int ID )
+{
+ QString command = QString( "SELECT name FROM categories WHERE id=%1;" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode( toLoad.value( 0 ).toCString() ) );
+
+ return ( QString::null );
+}
+
+QString QSqlRecipeDB::ingredientName( int ID )
+{
+ QString command = QString( "SELECT name FROM ingredients WHERE id=%1" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode( toLoad.value( 0 ).toCString() ) );
+
+ return ( QString::null );
+}
+
+QString QSqlRecipeDB::prepMethodName( int ID )
+{
+ QString command = QString( "SELECT name FROM prep_methods WHERE id=%1" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode( toLoad.value( 0 ).toCString() ) );
+
+ return ( QString::null );
+}
+
+IngredientProperty QSqlRecipeDB::propertyName( int ID )
+{
+ QString command = QString( "SELECT name,units FROM ingredient_properties WHERE id=%1;" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) { // Go to the first record (there should be only one anyway.
+ return ( IngredientProperty( unescapeAndDecode( toLoad.value( 0 ).toCString() ), unescapeAndDecode( toLoad.value( 1 ).toCString() ), ID ) );
+ }
+
+ return ( IngredientProperty( QString::null, QString::null ) );
+}
+
+Unit QSqlRecipeDB::unitName( int ID )
+{
+ QString command = QString( "SELECT name,plural,name_abbrev,plural_abbrev,type FROM units WHERE id=%1" ).arg( ID );
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() && toLoad.next() ) { // Go to the first record (there should be only one anyway.
+ Unit unit( unescapeAndDecode( toLoad.value( 0 ).toCString() ), unescapeAndDecode( toLoad.value( 1 ).toCString() ) );
+
+ //if we don't have both name and plural, use what we have as both
+ if ( unit.name.isEmpty() )
+ unit.name = unit.plural;
+ else if ( unit.plural.isEmpty() )
+ unit.plural = unit.name;
+
+ unit.name_abbrev = unescapeAndDecode( toLoad.value( 2 ).toCString() );
+ unit.plural_abbrev = unescapeAndDecode( toLoad.value( 3 ).toCString() );
+ unit.type = (Unit::Type) toLoad.value( 4 ).toInt();
+ unit.id = ID;
+
+ return unit;
+ }
+
+ return Unit();
+}
+
+int QSqlRecipeDB::getCount( const QString &table_name )
+{
+ m_command = "SELECT COUNT(1) FROM "+table_name;
+ QSqlQuery count( m_command, database );
+ if ( count.isActive() && count.next() ) { // Go to the first record (there should be only one anyway.
+ return count.value( 0 ).toInt();
+ }
+
+ return -1;
+}
+
+int QSqlRecipeDB::categoryTopLevelCount()
+{
+ m_command = "SELECT COUNT(1) FROM categories WHERE parent_id='-1'";
+ QSqlQuery count( m_command, database );
+ if ( count.isActive() && count.next() ) { // Go to the first record (there should be only one anyway.
+ return count.value( 0 ).toInt();
+ }
+
+ return -1;
+}
+
+bool QSqlRecipeDB::checkIntegrity( void )
+{
+
+
+ // Check existence of the necessary tables (the database may be created, but empty)
+ QStringList tables;
+ tables << "ingredient_info" << "ingredient_list" << "ingredient_properties" << "ingredient_weights" << "ingredients" << "recipes" << "unit_list" << "units" << "units_conversion" << "categories" << "category_list" << "authors" << "author_list" << "db_info" << "prep_methods" << "ingredient_groups" << "yield_types" << "prep_method_list" << "ratings" << "rating_criteria" << "rating_criterion_list";
+
+ QStringList existingTableList = database->tables();
+ for ( QStringList::Iterator it = tables.begin(); it != tables.end(); ++it ) {
+ bool found = false;
+
+ for ( QStringList::Iterator ex_it = existingTableList.begin(); ( ( ex_it != existingTableList.end() ) && ( !found ) ); ++ex_it ) {
+ found = ( *ex_it == *it );
+ }
+
+ if ( !found ) {
+ kdDebug() << "Recreating missing table: " << *it << "\n";
+ createTable( *it );
+ }
+ }
+
+ QStringList newTableList = database->tables();
+ if ( newTableList.isEmpty() )
+ return false;
+
+
+ // Check for older versions, and port
+
+ kdDebug() << "Checking database version...\n";
+ float version = databaseVersion();
+ kdDebug() << "version found... " << version << " \n";
+ kdDebug() << "latest version... " << latestDBVersion() << endl;
+ if ( int( qRound( databaseVersion() * 1e5 ) ) < int( qRound( latestDBVersion() * 1e5 ) ) ) { //correct for float's imprecision
+ switch ( KMessageBox::questionYesNo( 0, i18n( "<!doc>The database was created with a previous version of Krecipes. Would you like Krecipes to update this database to work with this version of Krecipes? Depending on the number of recipes and amount of data, this could take some time.<br><br><b>Warning: After updating, this database will no longer be compatible with previous versions of Krecipes.<br><br>Cancelling this operation may result in corrupting the database.</b>" ) ) ) {
+ case KMessageBox::Yes:
+ emit progressBegin(0,QString::null,i18n("Porting database structure..."),50);
+ portOldDatabases( version );
+ emit progressDone();
+ break;
+ case KMessageBox::No:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void QSqlRecipeDB::splitCommands( QString& s, QStringList& sl )
+{
+ sl = QStringList::split( QRegExp( ";{1}(?!@)" ), s );
+}
+
+void QSqlRecipeDB::portOldDatabases( float /* version */ )
+{}
+
+float QSqlRecipeDB::databaseVersion( void )
+{
+
+ QString command = "SELECT ver FROM db_info";
+ QSqlQuery dbVersion( command, database );
+
+ if ( dbVersion.isActive() && dbVersion.next() )
+ return ( dbVersion.value( 0 ).toDouble() ); // There should be only one (or none for old DB) element, so go to first
+ else
+ return ( 0.2 ); // if table is empty, assume oldest (0.2), and port
+}
+
+void QSqlRecipeDB::loadRatingCriterion( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ QString command = "SELECT id,name FROM rating_criteria ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ QSqlQuery toLoad( command, database );
+ if ( toLoad.isActive() ) {
+ while ( toLoad.next() ) {
+ Element el;
+ el.id = toLoad.value( 0 ).toInt();
+ el.name = unescapeAndDecode( toLoad.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadCategories( ElementList *list, int limit, int offset )
+{
+ list->clear();
+
+ m_command = "SELECT id,name FROM categories ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ QSqlQuery categoryToLoad( m_command, database );
+ if ( categoryToLoad.isActive() ) {
+ while ( categoryToLoad.next() ) {
+ Element el;
+ el.id = categoryToLoad.value( 0 ).toInt();
+ el.name = unescapeAndDecode( categoryToLoad.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::loadCategories( CategoryTree *list, int limit, int offset, int parent_id, bool recurse )
+{
+ QString limit_str;
+ if ( parent_id == -1 ) {
+ emit progressBegin(0,QString::null,i18n("Loading category list"));
+ list->clear();
+
+ //only limit the number of top-level categories
+ limit_str = (limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset);
+ }
+
+ m_command = "SELECT id,name,parent_id FROM categories WHERE parent_id='"+QString::number(parent_id)+"' ORDER BY name "+limit_str;
+
+ QSqlQuery categoryToLoad( QString::null, database );
+ //categoryToLoad.setForwardOnly(true); //FIXME? Subcategories aren't loaded if this is enabled, even though we only go forward
+
+ categoryToLoad.exec(m_command);
+
+ if ( categoryToLoad.isActive() ) {
+ while ( categoryToLoad.next() ) {
+ emit progress();
+
+ int id = categoryToLoad.value( 0 ).toInt();
+ Element el;
+ el.id = id;
+ el.name = unescapeAndDecode( categoryToLoad.value( 1 ).toCString() );
+ CategoryTree *list_child = list->add( el );
+
+ if ( recurse ) {
+ //QTime dbg_timer; dbg_timer.start(); kdDebug()<<" calling QSqlRecipeDB::loadCategories"<<endl;
+ loadCategories( list_child, -1, -1, id ); //limit and offset won't be used
+ // kdDebug()<<" done in "<<dbg_timer.elapsed()<<" ms"<<endl;
+ }
+ }
+ }
+
+ if ( parent_id == -1 )
+ emit progressDone();
+}
+
+void QSqlRecipeDB::createNewCategory( const QString &categoryName, int parent_id )
+{
+ QString command;
+ QString real_name = categoryName.left( maxCategoryNameLength() );
+
+ command = QString( "INSERT INTO categories VALUES(%3,'%1',%2);" )
+ .arg( escapeAndEncode( real_name ) )
+ .arg( parent_id )
+ .arg( getNextInsertIDStr( "categories", "id" ) );
+ QSqlQuery categoryToCreate( command, database );
+
+ emit categoryCreated( Element( real_name, lastInsertID() ), parent_id );
+}
+
+void QSqlRecipeDB::modCategory( int categoryID, const QString &newLabel )
+{
+ QString command = QString( "UPDATE categories SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( categoryID );
+ QSqlQuery categoryToUpdate( command, database );
+
+ emit categoryModified( Element( newLabel, categoryID ) );
+}
+
+void QSqlRecipeDB::modCategory( int categoryID, int new_parent_id )
+{
+ QString command = QString( "UPDATE categories SET parent_id=%1 WHERE id=%2;" ).arg( new_parent_id ).arg( categoryID );
+ QSqlQuery categoryToUpdate( command, database );
+
+ emit categoryModified( categoryID, new_parent_id );
+}
+
+void QSqlRecipeDB::removeCategory( int categoryID )
+{
+ QString command;
+
+ command = QString( "DELETE FROM categories WHERE id=%1;" ).arg( categoryID );
+ QSqlQuery categoryToRemove( command, database );
+
+ command = QString( "DELETE FROM category_list WHERE category_id=%1;" ).arg( categoryID );
+ categoryToRemove.exec( command );
+
+ //recursively delete subcategories
+ command = QString( "SELECT id FROM categories WHERE parent_id=%1;" ).arg( categoryID );
+ categoryToRemove.exec( command );
+ if ( categoryToRemove.isActive() ) {
+ while ( categoryToRemove.next() ) {
+ removeCategory( categoryToRemove.value( 0 ).toInt() );
+ }
+ }
+
+ emit categoryRemoved( categoryID );
+}
+
+
+void QSqlRecipeDB::loadAuthors( ElementList *list, int limit, int offset )
+{
+ list->clear();
+ QString command = "SELECT id,name FROM authors ORDER BY name"
+ +((limit==-1)?"":" LIMIT "+QString::number(limit)+" OFFSET "+QString::number(offset));
+ QSqlQuery authorToLoad( command, database );
+ if ( authorToLoad.isActive() ) {
+ while ( authorToLoad.next() ) {
+ Element el;
+ el.id = authorToLoad.value( 0 ).toInt();
+ el.name = unescapeAndDecode( authorToLoad.value( 1 ).toCString() );
+ list->append( el );
+ }
+ }
+}
+
+void QSqlRecipeDB::createNewAuthor( const QString &authorName )
+{
+ QString command;
+ QString real_name = authorName.left( maxAuthorNameLength() );
+
+ command = QString( "INSERT INTO authors VALUES(%2,'%1');" ).arg( escapeAndEncode( real_name ) ).arg( getNextInsertIDStr( "authors", "id" ) );
+ QSqlQuery authorToCreate( command, database );
+
+ emit authorCreated( Element( real_name, lastInsertID() ) );
+}
+
+void QSqlRecipeDB::modAuthor( int authorID, const QString &newLabel )
+{
+ QString command;
+
+ command = QString( "UPDATE authors SET name='%1' WHERE id=%2;" ).arg( escapeAndEncode( newLabel ) ).arg( authorID );
+ QSqlQuery authorToCreate( command, database );
+
+ emit authorRemoved( authorID );
+ emit authorCreated( Element( newLabel, authorID ) );
+}
+
+void QSqlRecipeDB::removeAuthor( int authorID )
+{
+ QString command;
+
+ command = QString( "DELETE FROM authors WHERE id=%1;" ).arg( authorID );
+ QSqlQuery authorToRemove( command, database );
+
+ emit authorRemoved( authorID );
+}
+
+int QSqlRecipeDB::findExistingAuthorByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxAuthorNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM authors WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingCategoryByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxCategoryNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM categories WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingIngredientGroupByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxIngGroupNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM ingredient_groups WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingIngredientByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxIngredientNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM ingredients WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingPrepByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxPrepMethodNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM prep_methods WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingPropertyByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxPropertyNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM ingredient_properties WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingUnitByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxUnitNameLength() ) ); //truncate to the maximum size db holds
+
+ QString command = "SELECT id FROM units WHERE name LIKE '" + search_str
+ + "' OR plural LIKE '" + search_str
+ + "' OR name_abbrev LIKE '" + search_str
+ + "' OR plural_abbrev LIKE '" + search_str
+ + "'";
+
+ QSqlQuery elementToLoad( command, database ); // Run the query
+ int id = -1;
+
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingRatingByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM rating_criteria WHERE name LIKE '%1'" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+
+ int id = -1;
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingRecipeByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxRecipeTitleLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM recipes WHERE title LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+
+ int id = -1;
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+int QSqlRecipeDB::findExistingYieldTypeByName( const QString& name )
+{
+ QString search_str = escapeAndEncode( name.left( maxYieldTypeLength() ) ); //truncate to the maximum size db holds
+
+ QString command = QString( "SELECT id FROM yield_types WHERE name LIKE '%1';" ).arg( search_str );
+ QSqlQuery elementToLoad( command, database ); // Run the query
+
+ int id = -1;
+ if ( elementToLoad.isActive() && elementToLoad.first() )
+ id = elementToLoad.value( 0 ).toInt();
+
+ return id;
+}
+
+void QSqlRecipeDB::mergeAuthors( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE author_list SET author_id=%1 WHERE author_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT recipe_id FROM author_list WHERE author_id=%1 ORDER BY recipe_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM author_list WHERE author_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM author_list WHERE author_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO author_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ //remove author with id 'id2'
+ command = QString( "DELETE FROM authors WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit authorRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeCategories( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE category_list SET category_id=%1 WHERE category_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT recipe_id FROM category_list WHERE category_id=%1 ORDER BY recipe_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM category_list WHERE category_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM category_list WHERE category_id=%1 AND recipe_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO category_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ command = QString( "UPDATE categories SET parent_id=%1 WHERE parent_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //we don't want to have a category be its own parent...
+ command = QString( "UPDATE categories SET parent_id=-1 WHERE parent_id=id" );
+ update.exec( command );
+
+ //remove category with id 'id2'
+ command = QString( "DELETE FROM categories WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+
+ emit categoriesMerged( id1, id2 );
+}
+
+void QSqlRecipeDB::mergeIngredientGroups( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE ingredient_list SET group_id=%1 WHERE group_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove ingredient with id 'id2'
+ command = QString( "DELETE FROM ingredient_groups WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit ingGroupRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeIngredients( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE ingredient_list SET ingredient_id=%1 WHERE ingredient_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //delete nutrient info associated with ingredient with id 'id2'
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1" )
+ .arg( id2 );
+ update.exec( command );
+
+ //update the unit_list
+ command = QString( "UPDATE unit_list SET ingredient_id=%1 WHERE ingredient_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT unit_id FROM unit_list WHERE ingredient_id=%1 ORDER BY unit_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO unit_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ //update ingredient info
+ command = QString( "UPDATE ingredient_info SET ingredient_id=%1 WHERE ingredient_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ //info associated with one ingredient will be lost... they should be the same ingredient and thus info anyways
+ command = QString( "SELECT property_id FROM ingredient_info WHERE ingredient_id=%1 ORDER BY property_id" )
+ .arg( id1 );
+ update.exec( command );
+ last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM ingredient_info WHERE ingredient_id=%1 AND property_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO ingredient_info VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+
+ }
+ last_id = current_id;
+ }
+ }
+
+ //remove ingredient with id 'id2'
+ command = QString( "DELETE FROM ingredients WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit ingredientRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergePrepMethods( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1' in ingredient list
+ QString command = QString( "UPDATE prep_method_list SET prep_method_id=%1 WHERE prep_method_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in ingredient weights
+ command = QString( "UPDATE ingredient_weights SET prep_method_id=%1 WHERE prep_method_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove prep method with id 'id2'
+ command = QString( "DELETE FROM prep_methods WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit prepMethodRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeProperties( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1'
+ QString command = QString( "UPDATE ingredient_properties SET id=%1 WHERE id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ command = QString( "UPDATE ingredient_info SET property_id=%1 WHERE property_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove prep method with id 'id2'
+ command = QString( "DELETE FROM ingredient_properties WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit propertyRemoved( id2 );
+}
+
+void QSqlRecipeDB::mergeUnits( int id1, int id2 )
+{
+ QSqlQuery update( QString::null, database );
+
+ //change all instances of 'id2' to 'id1' in unit list
+ QString command = QString( "UPDATE unit_list SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in ingredient list
+ command = QString( "UPDATE ingredient_list SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure no duplicates were created in this process
+ command = QString( "SELECT ingredient_id FROM unit_list WHERE unit_id=%1 ORDER BY ingredient_id" )
+ .arg( id1 );
+ update.exec( command );
+ int last_id = -1;
+ if ( update.isActive() ) {
+ while ( update.next() ) {
+ int current_id = update.value( 0 ).toInt();
+ if ( last_id == current_id ) {
+ int count = -1;
+ command = QString( "SELECT COUNT(1) FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ QSqlQuery remove( command, database);
+ if ( remove.isActive() && remove.first() )
+ count = remove.value(0).toInt();
+ if ( count > 1 ) {
+ command = QString( "DELETE FROM unit_list WHERE ingredient_id=%1 AND unit_id=%2" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+
+ command = QString( "INSERT INTO unit_list VALUES(%1,%2)" )
+ .arg( id1 )
+ .arg( last_id );
+ database->exec( command );
+ }
+ }
+ last_id = current_id;
+ }
+ }
+
+ //update ingredient info
+ command = QString( "UPDATE ingredient_info SET per_units=%1 WHERE per_units=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in unit_conversion
+ command = QString( "UPDATE units_conversion SET unit1_id=%1 WHERE unit1_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+ command = QString( "UPDATE units_conversion SET unit2_id=%1 WHERE unit2_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //and ensure that the one to one ratio wasn't created
+ command = QString( "DELETE FROM units_conversion WHERE unit1_id=unit2_id" );
+ update.exec( command );
+
+ //update ingredient weights
+ command = QString( "UPDATE ingredient_weights SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+ command = QString( "UPDATE ingredient_weights SET weight_unit_id=%1 WHERE weight_unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //change all instances of 'id2' to 'id1' in ingredient weights
+ command = QString( "UPDATE ingredient_weights SET unit_id=%1 WHERE unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ command = QString( "UPDATE ingredient_weights SET weight_unit_id=%1 WHERE weight_unit_id=%2" )
+ .arg( id1 )
+ .arg( id2 );
+ update.exec( command );
+
+ //remove units with id 'id2'
+ command = QString( "DELETE FROM units WHERE id=%1" ).arg( id2 );
+ update.exec( command );
+ emit unitRemoved( id2 );
+}
+
+QString QSqlRecipeDB::getUniqueRecipeTitle( const QString &recipe_title )
+{
+ //already is unique
+ if ( findExistingRecipeByName( recipe_title ) == -1 )
+ return recipe_title;
+
+ QString return_title = recipe_title; //If any error is produced, just go for default value (always return something)
+
+ QString command = QString( "SELECT COUNT(1) FROM recipes WHERE title LIKE '%1 (%)';" ).arg( escapeAndEncode( recipe_title ) );
+
+ QSqlQuery alikeRecipes( command, database );
+ if ( alikeRecipes.isActive() && alikeRecipes.first() )
+ {
+ int count = alikeRecipes.value( 0 ).toInt();
+ return_title = QString( "%1 (%2)" ).arg( recipe_title ).arg( count + 2 );
+
+ //make sure this newly created title is unique (just in case)
+ while ( findExistingRecipeByName( return_title ) != -1 ) {
+ count--; //go down to find the skipped recipe(s)
+ return_title = QString( "%1 (%2)" ).arg( recipe_title ).arg( count + 2 );
+ }
+ }
+
+ return return_title;
+}
+
+QString QSqlRecipeDB::recipeTitle( int recipeID )
+{
+ QString command = QString( "SELECT title FROM recipes WHERE id=%1;" ).arg( recipeID );
+ QSqlQuery recipeToLoad( command, database );
+ if ( recipeToLoad.isActive() && recipeToLoad.next() ) // Go to the first record (there should be only one anyway.
+ return ( unescapeAndDecode(recipeToLoad.value( 0 ).toCString()) );
+
+ return ( QString::null );
+}
+
+void QSqlRecipeDB::emptyData( void )
+{
+ QStringList tables;
+ tables << "ingredient_info" << "ingredient_list" << "ingredient_properties" << "ingredients" << "recipes" << "unit_list" << "units" << "units_conversion" << "categories" << "category_list" << "authors" << "author_list" << "prep_methods" << "ingredient_groups" << "yield_types" << "ratings" << "rating_criteria" << "rating_criterion_list";
+ QSqlQuery tablesToEmpty( QString::null, database );
+ for ( QStringList::Iterator it = tables.begin(); it != tables.end(); ++it ) {
+ QString command = QString( "DELETE FROM %1;" ).arg( *it );
+ tablesToEmpty.exec( command );
+ }
+}
+
+void QSqlRecipeDB::empty( void )
+{
+ QSqlQuery tablesToEmpty( QString::null, database );
+
+ QStringList list = database->tables();
+ QStringList::const_iterator it = list.begin();
+ while( it != list.end() ) {
+ QString command = QString( "DROP TABLE %1;" ).arg( *it );
+ tablesToEmpty.exec( command );
+
+ if ( !tablesToEmpty.isActive() )
+ kdDebug()<<tablesToEmpty.lastError().databaseText()<<endl;
+
+ ++it;
+ }
+}
+
+QString QSqlRecipeDB::getNextInsertIDStr( const QString &table, const QString &column )
+{
+ int next_id = getNextInsertID( table, column );
+
+ QString id_str;
+ if ( next_id == -1 )
+ id_str = "NULL";
+ else
+ id_str = QString::number( next_id );
+
+ return id_str;
+}
+
+void QSqlRecipeDB::search( RecipeList *list, int items, const RecipeSearchParameters &parameters )
+{
+ QString query = buildSearchQuery(parameters);
+
+ QValueList<int> ids;
+ QSqlQuery recipeToLoad( query, database );
+ if ( recipeToLoad.isActive() ) {
+ while ( recipeToLoad.next() ) {
+ ids << recipeToLoad.value( 0 ).toInt();
+ }
+ }
+
+ if ( ids.count() > 0 )
+ loadRecipes( list, items, ids );
+}
+
+#include "qsqlrecipedb.moc"
diff --git a/krecipes/src/backends/qsqlrecipedb.h b/krecipes/src/backends/qsqlrecipedb.h
new file mode 100644
index 0000000..68ba6cc
--- /dev/null
+++ b/krecipes/src/backends/qsqlrecipedb.h
@@ -0,0 +1,228 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef QSQLRECIPEDB_H
+#define QSQLRECIPEDB_H
+
+#include "backends/recipedb.h"
+
+#include <qglobal.h>
+#include <qobject.h>
+#include <qsqldatabase.h>
+#include <qimage.h>
+#include <qfileinfo.h>
+#include <qregexp.h>
+#include <qstring.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/unitratiolist.h"
+
+/**
+@author Unai Garro, Jason Kivlighn
+*/
+class QSqlRecipeDB : public RecipeDB
+{
+
+ Q_OBJECT
+
+protected:
+ virtual QString qsqlDriverPlugin() const { return QString::null; }
+ virtual QSqlDriver *qsqlDriver() const { return 0; }
+ virtual void createDB( void ) = 0;
+
+ virtual void portOldDatabases( float version );
+ virtual void storePhoto( int recipeID, const QByteArray &data );
+ virtual void loadPhoto( int recipeID, QPixmap &photo );
+ void loadRecipeMetadata( Recipe *recipe );
+
+ void search( RecipeList *list, int items, const RecipeSearchParameters & );
+
+ /** Return the next id for the given table and column.
+ * If the database supports getting this afterwards,
+ * leave the default implementation which returns -1.
+ *
+ * Note: Only call when an insert is actually going to take place.
+ * This function will increment the sequence counter.
+ */
+ virtual int getNextInsertID( const QString & /*table*/, const QString & /*column*/ )
+ {
+ return -1;
+ }
+
+ QSqlDatabase *database;
+ QSqlQuery m_query;
+ QString DBuser;
+ QString DBpass;
+ QString DBhost;
+ int DBport;
+
+public:
+ QSqlRecipeDB( const QString &host, const QString &user = QString::null, const QString &pass = QString::null, const QString &DBName = DEFAULT_DB_NAME, int port = 0 );
+ ~QSqlRecipeDB( void );
+
+ void connect( bool create_db, bool create_tables );
+
+ void addIngredientWeight( const Weight & );
+ void addProperty( const QString &name, const QString &units );
+ void addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID );
+ void addUnitToIngredient( int ingredientID, int unitID );
+
+ void categorizeRecipe( int recipeID, const ElementList &categoryList );
+ void changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units );
+
+ void createNewAuthor( const QString &authorName );
+ void createNewCategory( const QString &categoryName, int parent_id = -1 );
+ void createNewIngGroup( const QString &name );
+ void createNewIngredient( const QString &ingredientName );
+ void createNewPrepMethod( const QString &prepMethodName );
+ void createNewRating( const QString &name );
+ void createNewUnit( const Unit &unit );
+ void createNewYieldType( const QString &type );
+
+ void emptyData( void );
+ void empty( void );
+
+ int findExistingAuthorByName( const QString& name );
+ int findExistingCategoryByName( const QString& name );
+ int findExistingIngredientGroupByName( const QString& name );
+ int findExistingIngredientByName( const QString& name );
+ int findExistingPrepByName( const QString& name );
+ int findExistingRecipeByName( const QString& name );
+ int findExistingRatingByName( const QString& name );
+ int findExistingUnitByName( const QString& name );
+ int findExistingPropertyByName( const QString& name );
+ int findExistingYieldTypeByName( const QString& name );
+ void findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo );
+ void findIngredientDependancies( int ingredientID, ElementList *recipes );
+ void findPrepMethodDependancies( int prepMethodID, ElementList *recipes );
+ void findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights );
+ void findUseOfIngGroupInRecipes( ElementList *results, int groupID );
+ void findUseOfCategoryInRecipes( ElementList *results, int catID );
+ void findUseOfAuthorInRecipes( ElementList *results, int authorID );
+
+ QString getUniqueRecipeTitle( const QString &recipe_title );
+
+ bool ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID );
+ bool ingredientContainsUnit( int ingredientID, int unitID );
+
+ void loadAuthors( ElementList *list, int limit = -1, int offset = 0 );
+ void loadCategories( CategoryTree *list, int limit = -1, int offset = 0, int parent_id = -1, bool recurse = true );
+ void loadCategories( ElementList *list, int limit = -1, int offset = 0 );
+ void loadIngredientGroups( ElementList *list );
+ void loadIngredients( ElementList *list, int limit = -1, int offset = 0 );
+ void loadPossibleUnits( int ingredientID, UnitList *list );
+ void loadPrepMethods( ElementList *list, int limit = -1, int offset = 0 );
+ void loadProperties( IngredientPropertyList *list, int ingredientID = -2 ); // Loads the list of possible properties by default, all the ingredient properties with -1, and the ingredients of given property if id>=0
+ void loadRatingCriterion( ElementList *list, int limit = -1, int offset = 0 );
+ void loadRecipes( RecipeList *, int items = All, QValueList<int> ids = QValueList<int>() );
+ void loadRecipeList( ElementList *list, int categoryID = -1, bool recursive = false );
+ void loadUncategorizedRecipes( ElementList *list );
+ void loadUnits( UnitList *list, Unit::Type = Unit::All, int limit = -1, int offset = 0 );
+ void loadUnitRatios( UnitRatioList *ratioList, Unit::Type );
+ void loadYieldTypes( ElementList *list, int limit, int offset );
+
+ void mergeAuthors( int id1, int id2 );
+ void mergeCategories( int id1, int id2 );
+ void mergeIngredientGroups( int id1, int id2 );
+ void mergeIngredients( int id1, int id2 );
+ void mergeUnits( int id1, int id2 );
+ void mergePrepMethods( int id1, int id2 );
+ void mergeProperties( int id1, int id2 );
+
+ void modIngredientGroup( int ingredientID, const QString &newLabel );
+ /**
+ * set newLabel for ingredientID
+ */
+ void modIngredient( int ingredientID, const QString &newLabel );
+ /**
+ * set newLabel for unitID
+ */
+ void modUnit( const Unit &unit );
+ /**
+ * set newLabel for categoryID
+ */
+ void modCategory( int categoryID, const QString &newLabel );
+ void modCategory( int categoryID, int new_parent_id );
+ /**
+ * set newLabel for authorID
+ */
+ void modAuthor( int authorID, const QString &newLabel );
+
+ void modPrepMethod( int prepMethodID, const QString &newLabel );
+
+ void modProperty( int propertyID, const QString &newLabel );
+
+ QString recipeTitle( int recipeID );
+
+ void removeAuthor( int categoryID );
+ void removeCategory( int categoryID );
+ void removeIngredientGroup( int groupID );
+ void removeIngredient( int ingredientID );
+ void removeIngredientWeight( int id );
+ void removePrepMethod( int prepMethodID );
+ void removeProperty( int propertyID );
+ void removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID );
+ void removeRecipe( int id );
+ void removeRecipeFromCategory( int ingredientID, int categoryID );
+ void removeUnit( int unitID );
+ void removeUnitFromIngredient( int ingredientID, int unitID );
+ void removeUnitRatio( int unitID1, int unitID2 );
+
+ void saveRecipe( Recipe *recipe );
+ void saveUnitRatio( const UnitRatio *ratio );
+
+ double unitRatio( int unitID1, int unitID2 );
+ double ingredientWeight( const Ingredient &ing, bool *wasApproximated = 0 );
+ WeightList ingredientWeightUnits( int ingID );
+
+ QString escapeAndEncode( const QString &s ) const;
+ QString unescapeAndDecode( const QCString &s ) const;
+
+ QString categoryName( int ID );
+ QString prepMethodName( int ID );
+ QString ingredientName( int ID );
+ IngredientProperty propertyName( int ID );
+ Unit unitName( int ID );
+
+ int getCount( const QString &table_name );
+ int categoryTopLevelCount();
+
+ bool checkIntegrity( void );
+
+ void splitCommands( QString& s, QStringList& sl );
+
+ float databaseVersion( void );
+
+protected:
+ void execSQL( const QString &command );
+
+private:
+ void loadElementList( ElementList *elList, QSqlQuery *query );
+ void loadPropertyElementList( ElementList *elList, QSqlQuery *query );
+ QString getNextInsertIDStr( const QString &table, const QString &column );
+
+ QString DBname;
+ const QString connectionName;
+ QString m_command;
+
+ static int m_refCount;
+};
+
+
+
+
+#endif
diff --git a/krecipes/src/backends/recipedb.cpp b/krecipes/src/backends/recipedb.cpp
new file mode 100644
index 0000000..9df42df
--- /dev/null
+++ b/krecipes/src/backends/recipedb.cpp
@@ -0,0 +1,989 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "backends/recipedb.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <kprogress.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kprocess.h>
+#include <kprocio.h>
+#include <kfilterdev.h>
+#include <kmessagebox.h>
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+
+#include <map>
+
+#include "importers/kreimporter.h"
+
+#if HAVE_POSTGRESQL
+#include "PostgreSQL/psqlrecipedb.h"
+#endif
+
+#if HAVE_MYSQL
+#include "MySQL/mysqlrecipedb.h"
+#endif
+
+#if HAVE_SQLITE || HAVE_SQLITE3
+#include "SQLite/literecipedb.h"
+#endif
+
+#include "datablocks/categorytree.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/weight.h"
+
+#include "searchparameters.h"
+
+#include "usda_property_data.h"
+#include "usda_ingredient_data.h"
+#include "usda_unit_data.h"
+
+#define DB_FILENAME "krecipes.krecdb"
+
+struct ingredient_nutrient_data
+{
+ int usda_id;
+ QString name;
+ QValueList<double> data;
+ WeightList weights;
+};
+
+RecipeDB::RecipeDB() :
+ DCOPObject(),
+ QObject(), m_categoryCache(0), haltOperation(false)
+{
+ dbOK = false;
+ dbErr = "";
+}
+
+RecipeDB::~RecipeDB()
+{
+}
+
+double RecipeDB::latestDBVersion() const
+{
+ return 0.95;
+}
+
+QString RecipeDB::krecipes_version() const
+{
+ KInstance * this_instance = KGlobal::instance();
+ if ( this_instance && this_instance->aboutData() )
+ return this_instance->aboutData() ->version();
+
+ return QString::null; //Oh, well. We couldn't get the version (shouldn't happen).
+}
+
+RecipeDB* RecipeDB::createDatabase( const QString &dbType, const QString &file )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Server" );
+ QString host = config->readEntry( "Host", "localhost" );
+ QString user = config->readEntry( "Username", QString::null );
+ QString pass = config->readEntry( "Password", QString::null );
+ QString dbname = config->readEntry( "DBName", DEFAULT_DB_NAME );
+ int port = config->readNumEntry( "Port", 0 );
+
+ QString f = file;
+ if ( f.isEmpty() )
+ f = config->readEntry( "DBFile", locateLocal ( "appdata", DB_FILENAME ) );
+
+ return createDatabase( dbType, host, user, pass, dbname, port, f );
+}
+
+RecipeDB* RecipeDB::createDatabase( const QString &dbType, const QString &host, const QString &user, const QString &pass, const QString &dbname, int port, const QString &file )
+{
+ RecipeDB * database = 0;
+
+ if ( 0 )
+ ; //we need some condition here
+#if HAVE_SQLITE || HAVE_SQLITE3
+
+ else if ( dbType == "SQLite" ) {
+ database = new LiteRecipeDB( file );
+ }
+#endif //HAVE_SQLITE || HAVE_SQLITE3
+ #if HAVE_MYSQL
+ else if ( dbType == "MySQL" ) {
+ database = new MySQLRecipeDB( host, user, pass, dbname, port );
+ }
+#endif //HAVE_MYSQL
+ #if HAVE_POSTGRESQL
+ else if ( dbType == "PostgreSQL" ) {
+ database = new PSqlRecipeDB( host, user, pass, dbname, port );
+ }
+#endif //HAVE_POSTGRESQL
+ else {
+ kdDebug() << "No database support included (or available) for the " << dbType << " database." << endl;
+ }
+
+ return database;
+}
+
+void RecipeDB::updateCategoryCache( int limit )
+{
+ m_categoryCache = new CategoryTree;
+ loadCategories( m_categoryCache, limit, 0, -1, true );
+}
+
+void RecipeDB::clearCategoryCache()
+{
+ delete m_categoryCache;
+ m_categoryCache = 0;
+}
+
+void RecipeDB::loadCachedCategories( CategoryTree **list, int limit, int offset, int parent_id, bool recurse )
+{
+ if ( m_categoryCache ) {
+ if ( parent_id == -1 )
+ *list = m_categoryCache;
+ else //FIXME?: how slow is this find() call? the cache is loaded in sequential order, so should we iterate over the cache?
+ *list = m_categoryCache->find(parent_id);
+ //kdDebug() << "Loading category tree from the cache" << endl;
+ }
+ else {
+ loadCategories( *list, limit, offset, parent_id, recurse );
+ }
+}
+
+RecipeDB::ConversionStatus RecipeDB::convertIngredientUnits( const Ingredient &from, const Unit &to, Ingredient &result )
+{
+ result = from;
+
+ if ( from.units.id == to.id )
+ return Success;
+
+ if ( from.units.type == to.type && to.type != Unit::Other ) {
+ double ratio = unitRatio( from.units.id, to.id );
+ if ( ratio > 0 ) {
+ result.amount = from.amount * ratio;
+ result.units = to;
+
+ kdDebug()<<"Unit conversion SUCCESSFUL, from "<<unitName(from.units.id).name<<" to "<<unitName(to.id).name<<" for ingredient "<<ingredientName(from.ingredientID)<<" (ingredient not used in conversion)"<<endl;
+
+ return Success;
+ }
+ else {
+ kdDebug()<<"Unit conversion failed, you should probably update your unit conversion table."<<endl;
+ kdDebug()<<from.units.id<<" to "<<to.id<<endl;
+ return MissingUnitConversion;
+ }
+ }
+ else if ( to.type == Unit::Mass || from.units.type == Unit::Mass ) {
+ if ( from.ingredientID == -1 )
+ return MissingIngredient;
+
+ double fromToWeightRatio, weightToToRatio;
+ int unitID = -1;
+ int prepID = -2;
+
+ WeightList idList = ingredientWeightUnits( from.ingredientID );
+
+ if ( idList.count() == 0 )
+ return MissingIngredientWeight;
+
+ for ( WeightList::const_iterator it = idList.begin(); it != idList.end(); ++it ) {
+ //get conversion order correct (i.e., Mass -> Volume instead of Volume -> Mass, depending on unit type)
+ int first = (to.type == Unit::Mass)?(*it).perAmountUnitID:(*it).weightUnitID;
+ int second = (to.type == Unit::Mass)?(*it).weightUnitID:(*it).perAmountUnitID;
+ double tryFromToWeightRatio = unitRatio( from.units.id, first );
+ if ( tryFromToWeightRatio > 0 ) {
+ weightToToRatio = unitRatio( second, to.id );
+ fromToWeightRatio = tryFromToWeightRatio;
+ unitID = first;
+
+ kdDebug()<<"units work, is it the right prep method..."<<endl;
+ if ( from.prepMethodList.containsId( (*it).prepMethodID ) ) {
+ kdDebug()<<" yes"<<endl;
+ prepID = (*it).prepMethodID;
+ break;
+ }
+ kdDebug()<<" no, keep going"<<endl;
+ }
+ }
+ if ( unitID == -1 )
+ return MissingUnitConversion;
+
+ bool wasApproximated;
+
+ Ingredient i;
+ i.ingredientID = from.ingredientID;
+ i.units.id = unitID;
+ i.amount = from.amount * fromToWeightRatio;
+ i.prepMethodList = from.prepMethodList;
+ result.amount = ingredientWeight( i, &wasApproximated ) * weightToToRatio;
+ result.units = to;
+
+ if ( result.amount < 0 )
+ return MismatchedPrepMethod;
+ else if ( wasApproximated )
+ return MismatchedPrepMethodUsingApprox;
+
+ return Success;
+ }
+ else {
+ QString to_str;
+ switch ( to.type ) {
+ case Unit::Other: to_str = "Other"; break;
+ case Unit::Mass: to_str = "Mass"; break;
+ case Unit::Volume: to_str = "Volume"; break;
+ case Unit::All: kdDebug()<<"Code error: trying to convert to unit of type 'All'"<<endl; return InvalidTypes;
+ }
+ QString from_str;
+ switch ( from.units.type ) {
+ case Unit::Other: from_str = "Other"; break;
+ case Unit::Mass: from_str = "Mass"; break;
+ case Unit::Volume: from_str = "Volume"; break;
+ case Unit::All: kdDebug()<<"Code error: trying to convert from unit of type 'All'"<<endl; return InvalidTypes;
+ }
+ kdDebug()<<"Can't handle conversion from "<<from_str<<"("<<from.units.id<<") to "<<to_str<<"("<<to.id<<")"<<endl;
+
+ return InvalidTypes;
+ }
+}
+
+bool RecipeDB::backup( const QString &backup_file, QString *errMsg )
+{
+ kdDebug()<<"Backing up current database to "<<backup_file<<endl;
+
+ KProcess *p = new KProcess;
+ //p->setUseShell(true);
+
+ QIODevice *dumpFile = KFilterDev::deviceForFile(backup_file,"application/x-gzip");
+ if ( !dumpFile->open( IO_WriteOnly ) ) {
+ kdDebug()<<"Couldn't open "<<backup_file<<endl;
+ return false;
+ }
+
+ dumpStream = new QTextStream( dumpFile );
+
+ QStringList command = backupCommand();
+ if ( command.count() == 0 ) {
+ kdDebug()<<"Backup not available for this database backend"<<endl;
+ return false;
+ }
+
+ KConfig * config = kapp->config();
+ config->setGroup( "DBType" );
+
+ (*dumpStream) << "-- Generated for Krecipes v"<<krecipes_version()<<endl;
+ (*dumpStream) << "-- Krecipes database schema: "<<latestDBVersion()<<endl;
+ (*dumpStream) << "-- Krecipes database backend: "<<config->readEntry( "Type" )<<endl;
+
+ kdDebug()<<"Running '"<<command.first()<<"' to create backup file"<<endl;
+ *p << command /*<< ">" << backup_file*/;
+
+ QApplication::connect( p, SIGNAL(receivedStdout(KProcess*,char*,int)), this, SLOT(processDumpOutput(KProcess*,char*,int)) );
+ QApplication::connect( p, SIGNAL(receivedStderr(KProcess*,char*,int)), this, SLOT(processDumpOutput(KProcess*,char*,int)) );
+
+ emit progressBegin(0,QString::null,
+ QString("<center><b>%1</b></center>%2")
+ .arg(i18n("Creating complete backup"))
+ .arg(i18n("Depending on the number of recipes and amount of data, this could take some time.")),50);
+
+ bool success = p->start( KProcess::Block, KProcess::AllOutput );
+ if ( !success ) {
+ if ( errMsg ) *errMsg = QString(i18n("Unable to find or run the program '%1'. Either it is not installed on your system or it is not in $PATH.")).arg(command.first());
+ delete p;
+ delete dumpStream;
+ delete dumpFile;
+ QFile::remove(backup_file);
+ emit progressDone();
+ return false;
+ }
+
+ emit progressDone();
+
+ //User cancelled it; we'll still consider the operation successful,
+ //but delete the file we created
+ if ( !p->normalExit() ) {
+ kdDebug()<<"Process killed, deleting partial backup."<<endl;
+ QFile::remove(backup_file);
+ }
+
+ if ( p->exitStatus() != 0 ) {
+ //Since the process failed, dumpStream should have output from the app as to why it did
+ QString appOutput;
+ dumpFile->close();
+ if ( dumpFile->open( IO_ReadOnly ) ) {
+ QTextStream appErrStream( dumpFile );
+
+ //ignore our own versioning output
+ appErrStream.readLine();
+ appErrStream.readLine();
+ appErrStream.readLine();
+
+ appOutput = appErrStream.read();
+ }
+ else
+ kdDebug()<<"Unable to open file to get error output."<<endl;
+
+ if ( errMsg ) *errMsg = QString("%1\n%2").arg(i18n("Backup failed.")).arg(appOutput);
+ QFile::remove(backup_file);
+ delete p;
+ delete dumpStream;
+ delete dumpFile;
+ return false;
+ }
+
+ delete p;
+ delete dumpStream;
+ delete dumpFile;
+ return true;
+}
+
+void RecipeDB::processDumpOutput( KProcess *p, char *buffer, int buflen )
+{
+ int written = dumpStream->device()->writeBlock(buffer,buflen);
+ if ( written != buflen )
+ kdDebug()<<"Data lost: written ("<<written<<") != buflen ("<<buflen<<")"<<endl;
+
+ if ( haltOperation ) { haltOperation=false; p->kill(); return; }
+ emit progress();
+}
+
+void RecipeDB::initializeData( void )
+{
+ // Populate with data
+
+ // Read the commands form the data file
+ QFile datafile( KGlobal::dirs() ->findResource( "appdata", "data/data.sql" ) );
+ if ( datafile.open( IO_ReadOnly ) ) {
+ QTextStream stream( &datafile );
+ execSQL(stream);
+ datafile.close();
+ }
+}
+
+bool RecipeDB::restore( const QString &file, QString *errMsg )
+{
+ QIODevice *dumpFile = KFilterDev::deviceForFile(file,"application/x-gzip");
+ if ( dumpFile->open( IO_ReadOnly ) ) {
+
+ QTextStream stream( dumpFile );
+ QString firstLine = stream.readLine().stripWhiteSpace();
+ QString dbVersion = stream.readLine().stripWhiteSpace();
+ dbVersion = dbVersion.right( dbVersion.length() - dbVersion.find(":") - 2 );
+ if ( qRound(dbVersion.toDouble()*1e5) > qRound(latestDBVersion()*1e5) ) { //correct for float's imprecision
+ if ( errMsg ) *errMsg = i18n( "This backup was created with a newer version of Krecipes and cannot be restored." );
+ delete dumpFile;
+ return false;
+ }
+
+ KConfig * config = kapp->config();
+ config->setGroup( "DBType" );
+ QString dbType = stream.readLine().stripWhiteSpace();
+ dbType = dbType.right( dbType.length() - dbType.find(":") - 2 );
+ if ( dbType.isEmpty() || !firstLine.startsWith("-- Generated for Krecipes") ) {
+ if ( errMsg ) *errMsg = i18n("This file is not a Krecipes backup file or has become corrupt.");
+ delete dumpFile;
+ return false;
+ }
+ else if ( dbType != config->readEntry("Type",QString::null) ) {
+ if ( errMsg ) *errMsg = QString(i18n("This backup was created using the \"%1\" backend. It can only be restored into a database using this backend." )).arg(dbType);
+ delete dumpFile;
+ return false;
+ }
+
+
+ //We have to first wipe the database structure. Note that if we load a dump
+ //with from a previous version of Krecipes, the difference in structure
+ // wouldn't allow the data to be inserted. This remains forward-compatibity
+ //by loading the old schema and then porting it to the current version.
+ empty(); //the user had better be warned!
+
+ KProcIO *process = new KProcIO;
+
+ QStringList command = restoreCommand();
+ kdDebug()<<"Restoring backup using: "<<command[0]<<endl;
+ *process << command;
+
+ //process->setComm( KProcess::Stdin );
+ if ( process->start( KProcess::NotifyOnExit ) ) {
+ emit progressBegin(0,QString::null,
+ QString("<center><b>%1</b></center>%2")
+ .arg(i18n("Restoring backup"))
+ .arg(i18n("Depending on the number of recipes and amount of data, this could take some time.")));
+
+ do {
+ QByteArray array(4096);
+ int len = dumpFile->readBlock(array.data(),array.size());
+ array.resize(len);
+
+ if ( !process->writeStdin(array) )
+ kdDebug()<<"Yikes! Some input couldn't be written to the process!"<<endl;
+
+ if ( haltOperation ) { break; }
+ emit progress();
+ }
+ while ( !stream.atEnd() );
+
+ process->closeWhenDone();
+
+ //Since the process will exit when all stdin has been sent and processed,
+ //just loop until the process is no longer running. If something goes
+ //wrong, the user can still hit cancel.
+ int prog = 0;
+ while ( process->isRunning() ){
+ if ( haltOperation ) { break; }
+ kapp->processEvents();
+ if ( prog % 100 == 0 ) {
+ emit progress();
+ prog = 0;
+ }
+ ++prog;
+ }
+ }
+ else
+ kdDebug()<<"Unable to start process"<<endl;
+
+ delete process;
+ emit progressDone();
+
+ //Since we just loaded part of a file, the database won't be in a usable state.
+ //We'll wipe out the database structure and recreate it, leaving no data.
+ if ( haltOperation ) {
+ haltOperation=false;
+ empty();
+ checkIntegrity();
+ delete dumpFile;
+ if ( errMsg ) { *errMsg = i18n("Restore Failed"); }
+ return false;
+ }
+
+ dumpFile->close();
+
+ checkIntegrity();
+ }
+ else {
+ kdDebug()<<"Unable to open the selected backup file"<<endl;
+ return false;
+ }
+
+ delete dumpFile;
+ return true;
+}
+
+void RecipeDB::execSQL( QTextStream &stream )
+{
+ QString line, command;
+ while ( (line = stream.readLine()) != QString::null ) {
+ command += " "+line;
+ if ( command.startsWith(" --") ) {
+ command = QString::null;
+ }
+ else if ( command.endsWith(";") ) {
+ execSQL( command );
+ command = QString::null;
+ }
+ }
+}
+
+void RecipeDB::loadRecipe( Recipe *recipe, int items, int id )
+{
+ RecipeList rlist;
+ QValueList<int> ids; ids << id;
+ loadRecipes( &rlist, items, ids );
+
+ *recipe = *rlist.begin();
+}
+
+int RecipeDB::categoryCount()
+{
+ return getCount("categories");
+}
+
+int RecipeDB::authorCount()
+{
+ return getCount("authors");
+}
+
+int RecipeDB::ingredientCount()
+{
+ return getCount("ingredients");
+}
+
+int RecipeDB::prepMethodCount()
+{
+ return getCount("prep_methods");
+}
+
+int RecipeDB::unitCount()
+{
+ return getCount("units");
+}
+
+void RecipeDB::importSamples()
+{
+ QString sample_recipes = locate( "appdata", "data/samples-" + KGlobal::locale() ->language() + ".kreml" );
+ if ( sample_recipes.isEmpty() ) {
+ //TODO: Make this a KMessageBox??
+ kdDebug() << "NOTICE: Samples recipes for the language \"" << KGlobal::locale() ->language() << "\" are not available. However, if you would like samples recipes for this language included in future releases of Krecipes, we invite you to submit your own. Just save your favorite recipes in the kreml format and e-mail them to jkivlighn@gmail.com." << endl;
+
+ sample_recipes = locate( "appdata", "data/samples-en_US.kreml" ); //default to English
+ }
+ if ( !sample_recipes.isEmpty() ) {
+ KreImporter importer;
+
+ QStringList file;
+ file << sample_recipes;
+ importer.parseFiles( file );
+
+ importer.import( this, true );
+ }
+ else
+ kdDebug() << "Unable to find samples recipe file (samples-en_US.kreml)" << endl;
+}
+
+void RecipeDB::getIDList( const CategoryTree *categoryTree, QStringList &ids )
+{
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ ids << QString::number(child_it->category.id);
+ getIDList(child_it,ids );
+ }
+}
+
+QString RecipeDB::buildSearchQuery( const RecipeSearchParameters &p ) const
+{
+ QStringList queryList, conditionList, tableList;
+
+ if ( p.ingsOr.count() != 0 ) {
+ tableList << "ingredient_list il" << "ingredients i";
+ conditionList << "il.ingredient_id=i.id" << "il.recipe_id=r.id";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.ingsOr.begin(); it != p.ingsOr.end();) {
+ condition += "i.name LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.ingsOr.end() ) {
+ condition += "OR ";
+ }
+ }
+ condition += ")";
+
+ conditionList << condition;
+ }
+
+ if ( p.catsOr.count() != 0 ) {
+ tableList << "category_list cl" << "categories c";
+ conditionList << "cl.category_id=c.id" << "cl.recipe_id=r.id";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.catsOr.begin(); it != p.catsOr.end();) {
+ condition += "c.name LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.catsOr.end() ) {
+ condition += "OR ";
+ }
+ }
+ condition += ")";
+
+ conditionList << condition;
+ }
+
+ if ( p.authorsOr.count() != 0 ) {
+ tableList << "author_list al" << "authors a";
+ conditionList << "al.author_id=a.id" << "al.recipe_id=r.id";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.authorsOr.begin(); it != p.authorsOr.end();) {
+ condition += "a.name LIKE '%"+escapeAndEncode(*it)+"%'";
+ if ( ++it != p.authorsOr.end() ) {
+ condition += "OR ";
+ }
+ }
+ condition += ")";
+
+ conditionList << condition;
+ }
+
+ if ( p.titleKeywords.count() != 0 ) {
+ QString op = (p.requireAllTitleWords) ? "AND " : "OR ";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.titleKeywords.begin(); it != p.titleKeywords.end();) {
+ condition += "r.title LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.titleKeywords.end() ) {
+ condition += op;
+ }
+ }
+ condition += ")";
+ conditionList << condition;
+ }
+
+ if ( p.instructionsKeywords.count() != 0 ) {
+ QString op = (p.requireAllInstructionsWords) ? "AND " : "OR ";
+
+ QString condition = "(";
+ for ( QStringList::const_iterator it = p.instructionsKeywords.begin(); it != p.instructionsKeywords.end();) {
+ condition += "r.instructions LIKE '%"+escapeAndEncode(*it)+"%' ";
+ if ( ++it != p.instructionsKeywords.end() ) {
+ condition += op;
+ }
+ }
+ condition += ")";
+ conditionList << condition;
+ }
+
+ if ( !p.prep_time.isNull() ) {
+ QString op;
+ switch ( p.prep_param ) {
+ case 0: op = "<= "+p.prep_time.toString( "'hh:mm:ss'" ); break;
+ case 1: //TODO: have a configurable 'about'. It tests within 15 minutes for now.
+ QTime lower = p.prep_time; lower.addSecs( 60*15 );
+ QTime upper = p.prep_time; upper.addSecs( 60*-15 );
+ op = "BETWEEN "+lower.toString( "'hh:mm:ss'" )+" AND "+upper.toString( "'hh:mm:ss'" );
+ break;
+ }
+ conditionList << "r.prep_time "+op;
+ }
+
+ if ( p.servings > 0 ) {
+ QString op;
+ switch ( p.servings_param ) {
+ case 0: op = "> "+QString::number(p.servings); break;
+ case 1: op = "< "+QString::number(p.servings); break;
+ case 2: op = "BETWEEN "+QString::number(p.servings-5)+" AND "+QString::number(p.servings+5); break;
+ }
+ conditionList << "r.yield_amount "+op;
+ }
+
+ if ( p.createdDateBegin.isValid() ) {
+ if ( p.createdDateEnd.isValid() ) {
+ conditionList << "r.ctime >= '"+p.createdDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.ctime <= '"+p.createdDateEnd.toString(Qt::ISODate)+"'";
+ }
+ else {
+ if ( p.createdDateBegin.time().isNull() ) { //we just want something on a particular date, not time
+ QDateTime end = p.createdDateBegin.addDays(1);
+ conditionList << "r.ctime >= '"+p.createdDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.ctime <= '"+end.toString(Qt::ISODate)+"'";
+ }
+ else //use the exact time
+ conditionList << "r.ctime = '"+p.createdDateBegin.toString(Qt::ISODate)+"'";
+ }
+ }
+
+ if ( p.modifiedDateBegin.isValid() ) {
+ if ( p.modifiedDateEnd.isValid() ) {
+ conditionList << "r.mtime >= '"+p.modifiedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.mtime <= '"+p.modifiedDateEnd.toString(Qt::ISODate)+"'";
+ }
+ else {
+ if ( p.modifiedDateBegin.time().isNull() ) { //we just want something on a particular date, not time
+ QDateTime end = p.modifiedDateBegin.addDays(1);
+ conditionList << "r.mtime >= '"+p.modifiedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.mtime <= '"+end.toString(Qt::ISODate)+"'";
+ }
+ else //use the exact time
+ conditionList << "r.mtime = '"+p.modifiedDateBegin.toString(Qt::ISODate)+"'";
+ }
+ }
+
+ if ( p.accessedDateBegin.isValid() ) {
+ if ( p.accessedDateEnd.isValid() ) {
+ conditionList << "r.atime >= '"+p.accessedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.atime <= '"+p.accessedDateEnd.toString(Qt::ISODate)+"'";
+ }
+ else {
+ if ( p.accessedDateBegin.time().isNull() ) { //we just want something on a particular date, not time
+ QDateTime end = p.accessedDateBegin.addDays(1);
+ conditionList << "r.atime >= '"+p.accessedDateBegin.toString(Qt::ISODate)+"'";
+ conditionList << "r.atime <= '"+end.toString(Qt::ISODate)+"'";
+ }
+ else //use the exact time
+ conditionList << "r.atime = '"+p.accessedDateBegin.toString(Qt::ISODate)+"'";
+ }
+ }
+
+ QString wholeQuery = "SELECT r.id FROM recipes r"
+ +QString(tableList.count()!=0?","+tableList.join(","):"")
+ +QString(conditionList.count()!=0?" WHERE "+conditionList.join(" AND "):"");
+
+ kdDebug()<<"calling: "<<wholeQuery<<endl;
+ return wholeQuery+";";
+}
+
+//These are helper functions solely for use by the USDA data importer
+void getIngredientNameAndID( std::multimap<int, QString> * );
+int createUnit( const QString &name, Unit::Type, RecipeDB* );
+int createIngredient( const QString &name, int unit_g_id, int unit_mg_id, RecipeDB*, bool do_checks );
+void create_properties( RecipeDB* );
+
+void RecipeDB::importUSDADatabase()
+{
+ //check if the data file even exists before we do anything
+ QString abbrev_file = locate( "appdata", "data/abbrev.txt" );
+ if ( abbrev_file.isEmpty() ) {
+ kdDebug() << "Unable to find abbrev.txt data file." << endl;
+ return ;
+ }
+
+ QFile file( abbrev_file );
+ if ( !file.open( IO_ReadOnly ) ) {
+ kdDebug() << "Unable to open data file: " << abbrev_file << endl;
+ return ;
+ }
+
+ create_properties( this );
+
+ std::multimap<int, QString> *ings_and_ids = new std::multimap<int, QString>;
+ getIngredientNameAndID( ings_and_ids );
+
+ QTextStream stream( &file );
+ QValueList<ingredient_nutrient_data> *data = new QValueList<ingredient_nutrient_data>;
+
+ kdDebug() << "Parsing abbrev.txt" << endl;
+ while ( !stream.atEnd() ) {
+ QStringList fields = QStringList::split( "^", stream.readLine(), true );
+
+ int id = fields[ 0 ].mid( 1, fields[ 0 ].length() - 2 ).toInt();
+
+ std::multimap<int, QString>::iterator current_pair;
+ while ( ( current_pair = ings_and_ids->find( id ) ) != ings_and_ids->end() ) //there may be more than one ingredients with the same id
+ {
+ ingredient_nutrient_data current_ing;
+ current_ing.name = ( *current_pair ).second.latin1();
+
+ for ( int i = 2; i < TOTAL_USDA_PROPERTIES + 2; i++ ) //properties start at the third field (index 2)
+ current_ing.data << fields[ i ].toDouble();
+
+ Weight w;
+ w.weight = fields[ TOTAL_USDA_PROPERTIES + 2 ].toDouble();
+
+ QString amountAndWeight = fields[ TOTAL_USDA_PROPERTIES + 3 ].mid( 1, fields[ TOTAL_USDA_PROPERTIES + 3 ].length() - 2 );
+ if ( !amountAndWeight.isEmpty() ) {
+ int spaceIndex = amountAndWeight.find(" ");
+ w.perAmount = amountAndWeight.left(spaceIndex).toDouble();
+
+ QString perAmountUnit = amountAndWeight.right(amountAndWeight.length()-spaceIndex-1);
+
+ if ( parseUSDAUnitAndPrep( perAmountUnit, w.perAmountUnit, w.prepMethod ) )
+ current_ing.weights << w;
+ }
+
+ w = Weight();
+ w.weight = fields[ TOTAL_USDA_PROPERTIES + 4 ].toDouble();
+ amountAndWeight = fields[ TOTAL_USDA_PROPERTIES + 5 ].mid( 1, fields[ TOTAL_USDA_PROPERTIES + 5 ].length() - 2 );
+ if ( !amountAndWeight.isEmpty() ) {
+ int spaceIndex = amountAndWeight.find(" ");
+ w.perAmount = amountAndWeight.left(spaceIndex).toDouble();
+ QString perAmountUnit = amountAndWeight.right(amountAndWeight.length()-spaceIndex-1);
+
+ if ( parseUSDAUnitAndPrep( perAmountUnit, w.perAmountUnit, w.prepMethod ) )
+ current_ing.weights << w;
+ }
+
+ current_ing.usda_id = id;
+
+ data->append( current_ing );
+
+ ings_and_ids->erase( current_pair );
+ }
+ }
+
+ delete ings_and_ids;
+
+ //there's 13009 lines in the weight file
+ emit progressBegin( data->count(), i18n( "Nutrient Import" ), i18n( "Importing USDA nutrient data" ) );
+
+ //if there is no data in the database, we can really speed this up with this
+ bool do_checks = true;
+ {
+ ElementList ing_list;
+ loadIngredients( &ing_list );
+
+ if ( ing_list.count() == 0 ) {
+ kdDebug()<<"Found an empty database... enabling fast nutrient import"<<endl;
+ do_checks = false;
+ }
+ }
+
+ //since there are only two units used, lets just create them and store their id for speed
+ int unit_g_id = createUnit( "g", Unit::Mass, this );
+ int unit_mg_id = createUnit( "mg", Unit::Mass, this );
+
+ QValueList<ingredient_nutrient_data>::const_iterator it;
+ QValueList<ingredient_nutrient_data>::const_iterator data_end = data->end();
+ const int total = data->count();
+ int counter = 0;
+
+ for ( it = data->begin(); it != data_end; ++it ) {
+ counter++;
+ kdDebug() << "Inserting (" << counter << " of " << total << "): " << ( *it ).name << endl;
+
+ if ( haltOperation ) { haltOperation=false; break;}
+ emit progress();
+
+ int assigned_id = createIngredient( ( *it ).name, unit_g_id, unit_mg_id, this, do_checks );
+
+ //for now, only check if there is any info on the ingredient to see whether or not we will import this data,
+ //because checking to see that each property exists is quite slow
+ IngredientPropertyList ing_properties;
+ if ( do_checks ) loadProperties( &ing_properties, assigned_id );
+ if ( ing_properties.count() == 0 ) //ingredient doesn't already have any properties
+ {
+ QValueList<double>::const_iterator property_it;
+ QValueList<double>::const_iterator property_end = ( *it ).data.end();
+ int i = 0;
+ for ( property_it = ( *it ).data.begin(); property_it != property_end; ++property_it, ++i )
+ addPropertyToIngredient( assigned_id, property_data_list[ i ].id, ( *property_it ) / 100.0, unit_g_id );
+ }
+
+ WeightList existingWeights = ingredientWeightUnits( assigned_id );
+ const WeightList weights = (*it).weights;
+ for ( WeightList::const_iterator weight_it = weights.begin(); weight_it != weights.end(); ++weight_it ) {
+ Weight w = *weight_it;
+ w.perAmountUnitID = createUnit( w.perAmountUnit, Unit::Other, this );
+ w.weightUnitID = unit_g_id;
+ w.ingredientID = assigned_id;
+
+ //TODO optimze by creating all prep methods and storing them for faster non-db access
+ if ( !w.prepMethod.isEmpty() ) {
+ int prepID = findExistingPrepByName( w.prepMethod );
+ if ( prepID == -1 ) {
+ createNewPrepMethod( w.prepMethod );
+ prepID = lastInsertID();
+ }
+ w.prepMethodID = prepID;
+ }
+
+ bool exists = false;
+ for ( WeightList::const_iterator it = existingWeights.begin(); it != existingWeights.end(); ++it ) {
+ if ( (*it).perAmountUnitID == w.perAmountUnitID && (*it).prepMethodID == w.prepMethodID ) {
+ exists = true;
+ break;
+ }
+ }
+ if ( exists )
+ continue;
+
+ addIngredientWeight( w );
+ }
+ }
+
+ delete data;
+
+ kdDebug() << "USDA data import successful" << endl;
+
+ emit progressDone();
+}
+
+void getIngredientNameAndID( std::multimap<int, QString> *data )
+{
+ for ( int i = 0; !ingredient_data_list[ i ].name.isEmpty(); i++ )
+ data->insert( std::make_pair( ingredient_data_list[ i ].usda_id, ingredient_data_list[ i ].name ) );
+}
+
+int createIngredient( const QString &name, int unit_g_id, int unit_mg_id, RecipeDB *database, bool do_checks )
+{
+ bool ingredientExisted = true;
+ int assigned_id = -1;
+ if ( do_checks )
+ assigned_id = database->findExistingIngredientByName( name );
+
+ if ( assigned_id == -1 ) {
+ ingredientExisted = false;
+ database->createNewIngredient( name );
+ assigned_id = database->lastInsertID();
+ }
+
+ if ( !ingredientExisted || !database->ingredientContainsUnit( assigned_id, unit_g_id ) )
+ database->addUnitToIngredient( assigned_id, unit_g_id );
+
+ if ( !ingredientExisted || !database->ingredientContainsUnit( assigned_id, unit_mg_id ) )
+ database->addUnitToIngredient( assigned_id, unit_mg_id );
+
+ return assigned_id;
+}
+
+int createUnit( const QString &name, Unit::Type type, RecipeDB *database )
+{
+ int assigned_id = database->findExistingUnitByName( name );
+
+ if ( assigned_id == -1 ) //create unit since it doesn't exist
+ {
+ Unit unit(name, name);
+ unit.type = type;
+ database->createNewUnit( unit );
+ assigned_id = database->lastInsertID();
+ }
+ //keep what the user specified if the type here is Other
+ else if ( type != Unit::Other ) {
+ Unit unit = database->unitName(assigned_id);
+ if ( unit.type != type ) {
+ unit.type = type;
+ database->modUnit( unit );
+ }
+ }
+
+ return assigned_id;
+}
+
+void create_properties( RecipeDB *database )
+{
+ IngredientPropertyList property_list;
+ database->loadProperties( &property_list );
+
+ for ( int i = 0; !property_data_list[ i ].name.isEmpty(); i++ ) {
+ property_data_list[ i ].id = property_list.findByName( property_data_list[ i ].name );
+ if ( property_data_list[ i ].id == -1 ) //doesn't exist, so insert it and set property_data_list[i].id
+ {
+ database->addProperty( property_data_list[ i ].name, property_data_list[ i ].unit );
+ property_data_list[ i ].id = database->lastInsertID();
+ }
+ }
+}
+
+bool parseUSDAUnitAndPrep( const QString &string, QString &unit, QString &prep )
+{
+ int commaIndex = string.find(",");
+ QString unitPart = string.left(commaIndex);
+ QString prepPart = string.right(string.length()-commaIndex-2).stripWhiteSpace();
+
+ bool acceptable = false;
+ for ( int i = 0; unit_data_list[ i ].name; ++i ) {
+ if ( unitPart == unit_data_list[ i ].name || unitPart == unit_data_list[ i ].plural )
+ acceptable = true;
+ }
+ if ( !acceptable )
+ return false;
+
+ acceptable = false;
+ if ( prepPart.isEmpty() )
+ acceptable = true;
+ else {
+ for ( int i = 0; prep_data_list[ i ]; ++i ) {
+ if ( prepPart == prep_data_list[ i ] )
+ acceptable = true;
+ }
+ }
+ if ( !acceptable )
+ prepPart = QString::null;
+
+ unit = unitPart;
+ prep = prepPart;
+ return true;
+}
+
+#include "recipedb.moc"
diff --git a/krecipes/src/backends/recipedb.h b/krecipes/src/backends/recipedb.h
new file mode 100644
index 0000000..3901b56
--- /dev/null
+++ b/krecipes/src/backends/recipedb.h
@@ -0,0 +1,397 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEDB_H
+#define RECIPEDB_H
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#include <dcopclient.h>
+
+#include "krecipesdbiface.h"
+
+#include "datablocks/recipe.h"
+#include "datablocks/recipelist.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/unitratiolist.h"
+#include "datablocks/unit.h"
+
+#define DEFAULT_DB_NAME "Krecipes"
+
+/**
+@author Unai Garro
+*/
+
+class KProcess;
+class QTextStream;
+
+class CategoryTree;
+class RecipeSearchParameters;
+class Weight;
+class WeightList;
+
+typedef struct
+{
+ QValueList <int> recipeIdList;
+ IngredientList ilist;
+}
+RecipeIngredientList;
+
+class RecipeDB: public QObject, virtual public KrecipesDBIface
+{
+ Q_OBJECT
+
+public:
+ RecipeDB();
+ virtual ~RecipeDB();
+
+ virtual void connect( bool create_db = true, bool create_tables = true ) = 0;
+
+ void importSamples();
+
+ bool backup( const QString &file ){ return backup(file,0); }
+ bool backup( const QString &file, QString *errMsg = 0 );
+ bool restore( const QString &file, QString *errMsg = 0 );
+
+ // Error handling (passive)
+ bool dbOK;
+ QString dbErr;
+
+ enum RecipeItems {
+ None = 0,
+ NamesOnly = 256,
+ Noatime = 1024,
+ Photo = 1,
+ Instructions = 2,
+ Ingredients = 4,
+ Authors = 8,
+ Categories = 16,
+ PrepTime = 32,
+ Yield = 64,
+ Title = 128,
+ Meta = 512,
+ Ratings = 2048,
+ Properties = 4096,
+ IngredientAmounts = 8192,
+ All = 0xFFFF ^ NamesOnly ^ Noatime
+ };
+
+ typedef enum ConversionStatus {
+ Success,
+ MissingUnitConversion,
+ MissingIngredientWeight,
+ MissingIngredient,
+ InvalidTypes,
+ MismatchedPrepMethod,
+ MismatchedPrepMethodUsingApprox
+ };
+
+public slots:
+ void cancelOperation(){ haltOperation = true; }
+
+signals:
+ void progressBegin(int,const QString &c=QString::null,const QString &t=QString::null,int rate=1);
+ void progressDone();
+ void progress();
+
+ void authorCreated( const Element & );
+ void authorRemoved( int id );
+
+ void categoryCreated( const Element &, int parent_id );
+ void categoryRemoved( int id );
+ void categoryModified( const Element & );
+ void categoryModified( int id, int parent_id );
+ void categoriesMerged( int id1, int id2 );
+
+ void ingGroupCreated( const Element & );
+ void ingGroupRemoved( int id );
+
+ void ingredientCreated( const Element & );
+ void ingredientRemoved( int id );
+
+ void prepMethodCreated( const Element & );
+ void prepMethodRemoved( int id );
+
+ void propertyCreated( const IngredientProperty & );
+ void propertyRemoved( int id );
+
+ void unitCreated( const Unit & );
+ void unitRemoved( int id );
+
+ void ratingCriteriaCreated( const Element & );
+
+ void recipeCreated( const Element &, const ElementList &categories );
+ void recipeRemoved( int id );
+ void recipeRemoved( int id, int cat_id );
+ void recipeModified( const Element &, const ElementList &categories );
+
+ // Public methods
+public:
+ /** Returns a database object of the given type or NULL upon failure.
+ * This function should be called to create a new database, rather
+ * than directly calling the constructor of a specific backend.
+ */
+ static RecipeDB* createDatabase( const QString &dbType,
+ const QString &host,
+ const QString &user,
+ const QString &pass,
+ const QString &DBname,
+ int port,
+ const QString &file = QString::null );
+
+ /** Convenience method. Calls the above with arguments from KConfig. */
+ static RecipeDB* createDatabase( const QString &dbType, const QString &file = QString::null );
+
+ virtual void addIngredientWeight( const Weight & ) = 0;
+ virtual void addProperty( const QString &name, const QString &units ) = 0;
+ virtual void addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID ) = 0;
+ virtual void addUnitToIngredient( int ingredientID, int unitID ) = 0;
+
+ virtual void categorizeRecipe( int recipeID, const ElementList &categoryList ) = 0;
+ virtual void changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units ) = 0;
+
+ virtual void createNewAuthor( const QString &authorName ) = 0;
+ virtual void createNewCategory( const QString &categoryName, int parent_id = -1 ) = 0;
+ virtual void createNewIngGroup( const QString &name ) = 0;
+ virtual void createNewIngredient( const QString &ingredientName ) = 0;
+ virtual void createNewPrepMethod( const QString &prepMethodName ) = 0;
+ virtual void createNewRating( const QString &name ) = 0;
+ virtual void createNewUnit( const Unit &unit ) = 0;
+ virtual void createNewYieldType( const QString &type ) = 0;
+
+ virtual void emptyData( void ) = 0;
+ virtual void empty( void ) = 0;
+
+ virtual int findExistingAuthorByName( const QString& name ) = 0;
+ virtual int findExistingCategoryByName( const QString& name ) = 0;
+ virtual int findExistingIngredientGroupByName( const QString& name ) = 0;
+ virtual int findExistingIngredientByName( const QString& name ) = 0;
+ virtual int findExistingPrepByName( const QString& name ) = 0;
+ virtual int findExistingPropertyByName( const QString& name ) = 0;
+ virtual int findExistingRatingByName( const QString& name ) = 0;
+ virtual int findExistingRecipeByName( const QString& name ) = 0;
+ virtual int findExistingUnitByName( const QString& name ) = 0;
+ virtual int findExistingYieldTypeByName( const QString& name ) = 0;
+ virtual void findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo ) = 0;
+ virtual void findIngredientDependancies( int ingredientID, ElementList *recipes ) = 0;
+ virtual void findPrepMethodDependancies( int prepMethodID, ElementList *recipes ) = 0;
+ virtual void findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights ) = 0;
+ virtual void findUseOfIngGroupInRecipes( ElementList *results, int groupID ) = 0;
+ virtual void findUseOfCategoryInRecipes( ElementList *results, int catID ) = 0;
+ virtual void findUseOfAuthorInRecipes( ElementList *results, int authorID ) = 0;
+
+ void getIDList( const CategoryTree *categoryTree, QStringList &ids );
+ virtual QString getUniqueRecipeTitle( const QString &recipe_title ) = 0;
+ virtual void givePermissions( const QString &dbName, const QString &username, const QString &password = QString::null, const QString &clientHost = "localhost" ) = 0;
+
+ void importUSDADatabase();
+
+ virtual bool ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID ) = 0;
+ virtual bool ingredientContainsUnit( int ingredientID, int unitID ) = 0;
+
+ void initializeData( void );
+
+ virtual int lastInsertID() = 0;
+
+ virtual void loadAuthors( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadCategories( CategoryTree *list, int limit = -1, int offset = 0, int parent_id = -1, bool recurse = true ) = 0;
+ void loadCachedCategories( CategoryTree **list, int limit, int offset, int parent_id, bool recurse );
+ virtual void loadCategories( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadIngredientGroups( ElementList *list ) = 0;
+ virtual void loadIngredients( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadPossibleUnits( int ingredientID, UnitList *list ) = 0;
+ virtual void loadPrepMethods( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadProperties( IngredientPropertyList *list, int ingredientID = -2 ) = 0; // Loads the list of possible properties by default, all the ingredient properties with -1, and the ingredients of given property if id>=0
+ void loadRecipe( Recipe *recipe, int items, int id );
+
+ virtual void loadRatingCriterion( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+ /** Load all recipes with the ids in @param ids into the @ref RecipeList @param recipes */
+ virtual void loadRecipes( RecipeList *, int items = All, QValueList<int> ids = QValueList<int>()/*, KProgressDialog *progress_dlg = 0*/ ) = 0;
+ virtual void loadRecipeList( ElementList *list, int categoryID = -1, bool recursive = false ) = 0;
+ virtual void loadUncategorizedRecipes( ElementList *list ) = 0;
+ virtual void loadUnits( UnitList *list, Unit::Type = Unit::All, int limit = -1, int offset = 0 ) = 0;
+ virtual void loadUnitRatios( UnitRatioList *ratioList, Unit::Type ) = 0;
+ virtual void loadYieldTypes( ElementList *list, int limit = -1, int offset = 0 ) = 0;
+
+ /** Change all instances of authors with id @param id2 to @param id1 */
+ virtual void mergeAuthors( int id1, int id2 ) = 0;
+
+ /** Change all instances of categories with id @param id2 to @param id1 */
+ virtual void mergeCategories( int id1, int id2 ) = 0;
+
+ virtual void mergeIngredientGroups( int id1, int id2 ) = 0;
+
+ /** Change all instances of ingredients with id @param id2 to @param id1 */
+ virtual void mergeIngredients( int id1, int id2 ) = 0;
+
+ /** Change all instances of units with id @param id2 to @param id1 */
+ virtual void mergeUnits( int id1, int id2 ) = 0;
+
+ /** Change all instances of prep methods with id @param id2 to @param id1 */
+ virtual void mergePrepMethods( int id1, int id2 ) = 0;
+
+ virtual void mergeProperties( int id1, int id2 ) = 0;
+
+
+ virtual void modIngredientGroup( int ingredientID, const QString &newLabel ) = 0;
+ /**
+ * set newLabel for ingredientID
+ */
+ virtual void modIngredient( int ingredientID, const QString &newLabel ) = 0;
+ /**
+ * set newLabel for unitID
+ */
+ virtual void modUnit( const Unit &unit ) = 0;
+ /**
+ * set newLabel for categoryID
+ */
+ virtual void modCategory( int categoryID, const QString &newLabel ) = 0;
+ virtual void modCategory( int categoryID, int new_parent_id ) = 0;
+ /**
+ * set newLabel for authorID
+ */
+ virtual void modAuthor( int authorID, const QString &newLabel ) = 0;
+
+ virtual void modPrepMethod( int prepMethodID, const QString &newLabel ) = 0;
+
+ virtual void modProperty( int propertyID, const QString &newLabel ) = 0;
+
+ virtual QString recipeTitle( int recipeID ) = 0;
+
+ virtual void removeAuthor( int categoryID ) = 0;
+ virtual void removeCategory( int categoryID ) = 0;
+ virtual void removeIngredientGroup( int ingredientID ) = 0;
+ virtual void removeIngredient( int ingredientID ) = 0;
+ virtual void removeIngredientWeight( int id ) = 0;
+ virtual void removePrepMethod( int prepMethodID ) = 0;
+ virtual void removeProperty( int propertyID ) = 0;
+ virtual void removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID ) = 0;
+ virtual void removeRecipe( int id ) = 0;
+ virtual void removeRecipeFromCategory( int ingredientID, int categoryID ) = 0;
+ virtual void removeUnit( int unitID ) = 0;
+ virtual void removeUnitFromIngredient( int ingredientID, int unitID ) = 0;
+ virtual void removeUnitRatio( int unitID1, int unitID2 ) = 0;
+
+ virtual void saveRecipe( Recipe *recipe ) = 0;
+ virtual void saveUnitRatio( const UnitRatio *ratio ) = 0;
+ virtual void search( RecipeList *list, int items, const RecipeSearchParameters &parameters ) = 0;
+
+ /** @returns true on success, false otherwise */
+ ConversionStatus convertIngredientUnits( const Ingredient &from, const Unit &to, Ingredient &result );
+ virtual double unitRatio( int unitID1, int unitID2 ) = 0;
+
+ /** @returns the number of grams in the given amount of the ingredient, or -1 on failure */
+ virtual double ingredientWeight( const Ingredient &ing, bool *wasApproximated = 0 ) = 0;
+ virtual WeightList ingredientWeightUnits( int ingID ) = 0;
+
+ virtual QString escapeAndEncode( const QString &s ) const = 0;
+ virtual QString unescapeAndDecode( const QCString &s ) const = 0;
+
+ virtual QString categoryName( int ID ) = 0;
+ virtual QString ingredientName( int ID ) = 0;
+ virtual QString prepMethodName( int ID ) = 0;
+ virtual IngredientProperty propertyName( int ID ) = 0;
+ virtual Unit unitName( int ID ) = 0;
+
+ virtual int categoryTopLevelCount() = 0;
+ virtual int getCount( const QString &table_name ) = 0;
+ int authorCount();
+ int ingredientCount();
+ int prepMethodCount();
+ int unitCount();
+ int categoryCount();
+
+ virtual bool checkIntegrity( void ) = 0;
+
+ virtual void createTable( const QString &tableName ) = 0;
+ virtual void splitCommands( QString& s, QStringList& sl ) = 0;
+
+ virtual float databaseVersion( void ) = 0;
+
+ int maxAuthorNameLength() const
+ {
+ return 50;
+ }
+ int maxCategoryNameLength() const
+ {
+ return 40;
+ }
+ int maxIngredientNameLength() const
+ {
+ return 50;
+ }
+ int maxIngGroupNameLength() const
+ {
+ return 50;
+ }
+ int maxRecipeTitleLength() const
+ {
+ return 200;
+ }
+ int maxUnitNameLength() const
+ {
+ return 20;
+ }
+ int maxPrepMethodNameLength() const
+ {
+ return 20;
+ }
+ int maxPropertyNameLength() const
+ {
+ return 20;
+ }
+ int maxYieldTypeLength() const
+ {
+ return 20;
+ }
+
+ virtual bool ok()
+ {
+ return ( dbOK );
+ }
+ virtual QString err()
+ {
+ return ( dbErr );
+ }
+
+ void updateCategoryCache( int limit );
+ void clearCategoryCache();
+
+protected:
+ virtual void portOldDatabases( float version ) = 0;
+ virtual QStringList backupCommand() const = 0;
+ virtual QStringList restoreCommand() const = 0;
+
+ //Use these with caution: SQL for one backend might not work on another!
+ void execSQL( QTextStream &stream );
+ virtual void execSQL( const QString & ) = 0;
+
+ QString buildSearchQuery( const RecipeSearchParameters &parameters ) const;
+
+ double latestDBVersion() const;
+ QString krecipes_version() const;
+
+ CategoryTree *m_categoryCache;
+
+private:
+ QTextStream *dumpStream;
+ bool haltOperation;
+
+private slots:
+ void processDumpOutput( KProcess *, char *buffer, int buflen );
+};
+
+#endif
diff --git a/krecipes/src/backends/searchparameters.h b/krecipes/src/backends/searchparameters.h
new file mode 100644
index 0000000..c3e354a
--- /dev/null
+++ b/krecipes/src/backends/searchparameters.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+* Copyright (C) 2005 *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SEARCHPARAMETERS_H
+#define SEARCHPARAMETERS_H
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qdatetime.h>
+
+class RecipeSearchParameters
+{
+public:
+ RecipeSearchParameters() : servings(-1)/*, averageRating(-1), averageRatingOffset(0)*/
+ {}
+
+ QStringList titleKeywords;
+ bool requireAllTitleWords;
+
+ QStringList instructionsKeywords;
+ bool requireAllInstructionsWords;
+
+ QStringList ingsOr;
+ QStringList catsOr;
+ QStringList authorsOr;
+
+ QTime prep_time;
+
+ /** 0 -> greater than given time
+ * 1 -> less than given time
+ * 2 -> about given time
+ */
+ int prep_param;
+
+ int servings;
+
+ /** 0 -> greater than given time
+ * 1 -> less than given time
+ * 2 -> about given time
+ */
+ int servings_param;
+
+ QDateTime createdDateBegin;
+ QDateTime createdDateEnd;
+ QDateTime modifiedDateBegin;
+ QDateTime modifiedDateEnd;
+ QDateTime accessedDateBegin;
+ QDateTime accessedDateEnd;
+
+ //RatingCriteriaList criteriaList;
+ //double averageRating;
+ //double averageRatingOffset;
+};
+
+
+#endif
diff --git a/krecipes/src/backends/usda_ingredient_data.h b/krecipes/src/backends/usda_ingredient_data.h
new file mode 100644
index 0000000..f7ad2df
--- /dev/null
+++ b/krecipes/src/backends/usda_ingredient_data.h
@@ -0,0 +1,486 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDA_INGREDIENT_DATA_H
+#define USDA_INGREDIENT_DATA_H
+
+#include <klocale.h>
+
+struct ingredient_data
+{
+ QString name;
+ int usda_id;
+};
+
+static ingredient_data ingredient_data_list[] = {
+ {I18N_NOOP( "active baker's yeast" ), 18375},
+ {I18N_NOOP( "all-purpose flour" ), 20081},
+ {I18N_NOOP( "allspice" ), 2001},
+ {I18N_NOOP( "almond extract" ), 12071},
+ {I18N_NOOP( "almonds" ), 12061},
+ {I18N_NOOP( "apple juice" ), 9016},
+ {I18N_NOOP( "apple slices" ), 9008},
+ {I18N_NOOP( "apples" ), 9003},
+ {I18N_NOOP( "applesauce" ), 9402},
+ {I18N_NOOP( "applesauce, unsweetened" ), 9019},
+ {I18N_NOOP( "apricot jam" ), 19719},
+ {I18N_NOOP( "bacon" ), 10123},
+ {I18N_NOOP( "baking potato" ), 11362},
+ {I18N_NOOP( "baking powder" ), 18369},
+ {I18N_NOOP( "baking soda" ), 18372},
+ {I18N_NOOP( "bananas" ), 9040},
+ {I18N_NOOP( "barley" ), 20004},
+ {I18N_NOOP( "basil" ), 2003},
+ {I18N_NOOP( "bay leaf" ), 2004},
+ {I18N_NOOP( "bean sprouts" ), 11248},
+ {I18N_NOOP( "beans, black" ), 16014},
+ {I18N_NOOP( "beef bottom round" ), 13399},
+ {I18N_NOOP( "beef bouillon" ), 6075},
+ {I18N_NOOP( "beef broth" ), 6008},
+ {I18N_NOOP( "beef stock" ), 6476},
+ {I18N_NOOP( "beef, brisket" ), 13022},
+ {I18N_NOOP( "beer" ), 14003},
+ {I18N_NOOP( "bell peppers (red, green, yellow)" ), 11333},
+ {I18N_NOOP( "black pepper" ), 2030},
+ {I18N_NOOP( "boiling water" ), 14429},
+ {I18N_NOOP( "bologna" ), 7008},
+ {I18N_NOOP( "bourbon" ), 14551},
+ {I18N_NOOP( "bran flakes" ), 8153},
+ {I18N_NOOP( "bread" ), 18069},
+ {I18N_NOOP( "bread crumbs" ), 18079},
+ {I18N_NOOP( "bread cubes" ), 18079},
+ {I18N_NOOP( "broccoli" ), 11090},
+ {I18N_NOOP( "broccoli (frozen-thawed)" ), 11092},
+ {I18N_NOOP( "broccoli spears, frozen" ), 11094},
+ {I18N_NOOP( "broccoli, frozen" ), 11092},
+ {I18N_NOOP( "brown rice" ), 20040},
+ {I18N_NOOP( "brown sugar" ), 19334},
+ {I18N_NOOP( "bulgur" ), 20012},
+ {I18N_NOOP( "butter" ), 1145},
+ {I18N_NOOP( "butter (1/2 stick)" ), 1145},
+ {I18N_NOOP( "butter or margarine" ), 1145},
+ {I18N_NOOP( "buttermilk" ), 1088},
+ {I18N_NOOP( "cabbage" ), 11109},
+ {I18N_NOOP( "canned apple slices" ), 19312},
+ {I18N_NOOP( "canned beans" ), 16006},
+ {I18N_NOOP( "canned beef" ), 13934},
+ {I18N_NOOP( "canned black beans" ), 16018},
+ {I18N_NOOP( "canned carrot slices" ), 11128},
+ {I18N_NOOP( "canned cooked squid" ), 15175},
+ {I18N_NOOP( "canned corn" ), 11174},
+ {I18N_NOOP( "canned crushed tomatoes" ), 11693},
+ {I18N_NOOP( "canned green beans" ), 11056},
+ {I18N_NOOP( "canned green peas" ), 11308},
+ {I18N_NOOP( "canned jalapeno pepper" ), 11632},
+ {I18N_NOOP( "canned kidney beans" ), 16029},
+ {I18N_NOOP( "canned pinto beans" ), 16044},
+ {I18N_NOOP( "canned potato" ), 11376},
+ {I18N_NOOP( "canned red pepper" ), 2031},
+ {I18N_NOOP( "canned red tart cherries in water" ), 9065},
+ {I18N_NOOP( "canned sweet potatoes" ), 11514},
+ {I18N_NOOP( "canned tomatoes" ), 11693},
+ {I18N_NOOP( "canned wax beans" ), 16029},
+ {I18N_NOOP( "canned white beans" ), 16051},
+ {I18N_NOOP( "canned whole kernel corn" ), 11771},
+ {I18N_NOOP( "carrot" ), 11124},
+ {I18N_NOOP( "carrot slices, canned" ), 11128},
+ {I18N_NOOP( "carrots" ), 11124},
+ {I18N_NOOP( "carrots, frozen" ), 11130},
+ {I18N_NOOP( "carrots, peeled and chopped" ), 11124},
+ {I18N_NOOP( "catsup" ), 11935},
+ {I18N_NOOP( "cauliflower, frozen" ), 11137},
+ {I18N_NOOP( "cayenne" ), 2031},
+ {I18N_NOOP( "celery" ), 11143},
+ {I18N_NOOP( "celery leaf" ), 11143},
+ {I18N_NOOP( "celery salt" ), 2007},
+ {I18N_NOOP( "celery seed" ), 2007},
+ {I18N_NOOP( "cheddar cheese" ), 1009},
+ {I18N_NOOP( "cheese" ), 1009},
+ {I18N_NOOP( "cherry pie filling" ), 19314},
+ {I18N_NOOP( "chicken" ), 5011},
+ {I18N_NOOP( "chicken (1-2 lb)" ), 5011},
+ {I18N_NOOP( "chicken bouillon" ), 6081},
+ {I18N_NOOP( "chicken breast" ), 5062},
+ {I18N_NOOP( "chicken broth" ), 6013},
+ {I18N_NOOP( "chicken pieces" ), 5008},
+ {I18N_NOOP( "chicken stock" ), 6413},
+ {I18N_NOOP( "chicken thigh" ), 5080},
+ {I18N_NOOP( "chicken, cooked" ), 5011},
+ {I18N_NOOP( "chickens, whole" ), 5123},
+ {I18N_NOOP( "chili" ), 16059},
+ {I18N_NOOP( "chili powder" ), 2009},
+ {I18N_NOOP( "chinese turnip" ), 11564},
+ {I18N_NOOP( "chives" ), 11615},
+ {I18N_NOOP( "chocolate chip" ), 19139},
+ {I18N_NOOP( "chocolate pudding mix (instant)" ), 19123},
+ {I18N_NOOP( "chopped capers" ), 2054},
+ {I18N_NOOP( "chopped carrot" ), 11124},
+ {I18N_NOOP( "chopped celery" ), 11143},
+ {I18N_NOOP( "chopped chives" ), 11156},
+ {I18N_NOOP( "chopped green chilies" ), 11980},
+ {I18N_NOOP( "chopped onion" ), 11282},
+ {I18N_NOOP( "chopped onions" ), 11282},
+ {I18N_NOOP( "chopped onions, frozen" ), 11287},
+ {I18N_NOOP( "chopped parsley" ), 11297},
+ {I18N_NOOP( "chopped pimientos" ), 11943},
+ {I18N_NOOP( "chopped walnuts" ), 12154},
+ {I18N_NOOP( "cider vinegar" ), 2048},
+ {I18N_NOOP( "cilantro" ), 11971},
+ {I18N_NOOP( "cinnamon" ), 2010},
+ {I18N_NOOP( "clear honey" ), 19296},
+ {I18N_NOOP( "cloves" ), 2011},
+ {I18N_NOOP( "cloves garlic" ), 11215},
+ {I18N_NOOP( "cloves or allspice" ), 2011},
+ {I18N_NOOP( "cocoa" ), 19165},
+ {I18N_NOOP( "coconut" ), 12104},
+ {I18N_NOOP( "coconut milk (canned)" ), 12118},
+ {I18N_NOOP( "cold butter" ), 1145},
+ {I18N_NOOP( "cold water" ), 14429},
+ {I18N_NOOP( "condensed milk (sweetened)" ), 1095},
+ {I18N_NOOP( "cooked chicken" ), 5124},
+ {I18N_NOOP( "cooked chicken breast" ), 5065},
+ {I18N_NOOP( "cooked ham" ), 10151},
+ {I18N_NOOP( "cooked turkey" ), 5182},
+ {I18N_NOOP( "cooked wild rice" ), 20089},
+ {I18N_NOOP( "cookie crust (chocolate - 8-9 in)" ), 18398},
+ {I18N_NOOP( "Cool Whip" ), 1054},
+ {I18N_NOOP( "corn chips" ), 19003},
+ {I18N_NOOP( "corn meal" ), 20020},
+ {I18N_NOOP( "corn oil" ), 4518},
+ {I18N_NOOP( "corn starch" ), 20027},
+ {I18N_NOOP( "corn syrup" ), 19350},
+ {I18N_NOOP( "cornmeal" ), 20022},
+ {I18N_NOOP( "cornstarch" ), 20027},
+ {I18N_NOOP( "cottage cheese" ), 1015},
+ {I18N_NOOP( "cream (heavy)" ), 1053},
+ {I18N_NOOP( "cream cheese" ), 1017},
+ {I18N_NOOP( "cream of celery soup" ), 6479},
+ {I18N_NOOP( "cream of chicken soup" ), 6483},
+ {I18N_NOOP( "cream of chicken soup, condensed" ), 6016},
+ {I18N_NOOP( "cream of mushroom soup" ), 6443},
+ {I18N_NOOP( "cream of mushroom soup, condensed" ), 6043},
+ {I18N_NOOP( "croutons to serve" ), 18243},
+ {I18N_NOOP( "crushed pineapple in juice" ), 9268},
+ {I18N_NOOP( "cucumber" ), 11205},
+ {I18N_NOOP( "cucumber, whole" ), 11206},
+ {I18N_NOOP( "cucumbers" ), 11206},
+ {I18N_NOOP( "cucumbers, whole" ), 11206},
+ {I18N_NOOP( "cumin" ), 2014},
+ {I18N_NOOP( "currants" ), 9084},
+ {I18N_NOOP( "dehydrated onion flakes" ), 11284},
+ {I18N_NOOP( "diced chicken" ), 5039},
+ {I18N_NOOP( "dried basil" ), 2044},
+ {I18N_NOOP( "dried onion" ), 11284},
+ {I18N_NOOP( "dried oregano" ), 2027},
+ {I18N_NOOP( "dried parsley" ), 2029},
+ {I18N_NOOP( "dried porcini mushrooms" ), 11268},
+ {I18N_NOOP( "dried red pepper" ), 2031},
+ {I18N_NOOP( "dried sage" ), 2038},
+ {I18N_NOOP( "dry bread crumbs" ), 18079},
+ {I18N_NOOP( "dry mustard" ), 2024},
+ {I18N_NOOP( "dry nonfat milk powder" ), 1091},
+ {I18N_NOOP( "egg" ), 1123},
+ {I18N_NOOP( "egg noodles (medium)" ), 20409},
+ {I18N_NOOP( "egg substitute" ), 1144},
+ {I18N_NOOP( "eggplant" ), 11209},
+ {I18N_NOOP( "eggplant (baby)" ), 11209},
+ {I18N_NOOP( "eggs" ), 1134},
+ {I18N_NOOP( "eggs, hard cooked" ), 1129},
+ {I18N_NOOP( "eggs, hard-boiled" ), 1129},
+ {I18N_NOOP( "egg whites" ), 1124},
+ {I18N_NOOP( "egg yolk" ), 1125},
+ {I18N_NOOP( "egg yolks" ), 1125},
+ {I18N_NOOP( "elbow macaroni" ), 20099},
+ {I18N_NOOP( "flaked almonds" ), 12061},
+ {I18N_NOOP( "flat anchovies" ), 15001},
+ {I18N_NOOP( "flour" ), 20081},
+ {I18N_NOOP( "flour tortillas" ), 18364},
+ {I18N_NOOP( "flour, all-purpose" ), 20081},
+ {I18N_NOOP( "flour, white" ), 20581},
+ {I18N_NOOP( "flour, whole-grain wheat" ), 20080},
+ {I18N_NOOP( "fresh spinach" ), 11457},
+ {I18N_NOOP( "freshly chopped coriander" ), 11165},
+ {I18N_NOOP( "freshly chopped parsley" ), 11297},
+ {I18N_NOOP( "frozen broccoli" ), 11092},
+ {I18N_NOOP( "frozen broccoli spears" ), 11094},
+ {I18N_NOOP( "frozen cauliflower" ), 11137},
+ {I18N_NOOP( "frozen corn" ), 11179},
+ {I18N_NOOP( "frozen egg whites" ), 1172},
+ {I18N_NOOP( "frozen fish" ), 15027},
+ {I18N_NOOP( "frozen green beans" ), 11060},
+ {I18N_NOOP( "frozen orange juice concentrate" ), 9214},
+ {I18N_NOOP( "frozen peas" ), 11814},
+ {I18N_NOOP( "frozen whole egg" ), 1171},
+ {I18N_NOOP( "garlic" ), 11215},
+ {I18N_NOOP( "garlic clove" ), 11215},
+ {I18N_NOOP( "garlic cloves" ), 11215},
+ {I18N_NOOP( "garlic powder" ), 2020},
+ {I18N_NOOP( "garlic salt" ), 2020},
+ {I18N_NOOP( "gelatin" ), 19173},
+ {I18N_NOOP( "gelatin (peach-flavored)" ), 14397},
+ {I18N_NOOP( "ginger" ), 2021},
+ {I18N_NOOP( "ginger, fresh" ), 2021},
+ {I18N_NOOP( "graham cracker crust (8 or 9 in)" ), 18173},
+ {I18N_NOOP( "granny Smith apple" ), 9003},
+ {I18N_NOOP( "granulated garlic" ), 16125},
+ {I18N_NOOP( "grapefruit" ), 9112},
+ {I18N_NOOP( "gravy" ), 6116},
+ {I18N_NOOP( "green beans (fresh)" ), 11052},
+ {I18N_NOOP( "green chile" ), 11980},
+ {I18N_NOOP( "green chiles" ), 11670},
+ {I18N_NOOP( "green chili pepper" ), 11670},
+ {I18N_NOOP( "green onion" ), 11282},
+ {I18N_NOOP( "green pepper" ), 11333},
+ {I18N_NOOP( "green peppers" ), 11333},
+ {I18N_NOOP( "ground almonds" ), 12061},
+ {I18N_NOOP( "ground beef" ), 13309},
+ {I18N_NOOP( "ground chuck" ), 13302},
+ {I18N_NOOP( "ground cinnamon" ), 2010},
+ {I18N_NOOP( "ground clove" ), 2011},
+ {I18N_NOOP( "ground cloves" ), 2011},
+ {I18N_NOOP( "ground cumin" ), 2014},
+ {I18N_NOOP( "ground ginger" ), 2021},
+ {I18N_NOOP( "ground nutmeg" ), 2025},
+ {I18N_NOOP( "ground oregano" ), 2027},
+ {I18N_NOOP( "ground pepper" ), 2030},
+ {I18N_NOOP( "ground thyme" ), 2042},
+ {I18N_NOOP( "ham" ), 7027},
+ {I18N_NOOP( "hamburger" ), 13309},
+ {I18N_NOOP( "hamburger bun" ), 18641},
+ {I18N_NOOP( "hamburger buns" ), 18641},
+ {I18N_NOOP( "hamburger rolls" ), 18641},
+ {I18N_NOOP( "hazelnuts" ), 12120},
+ {I18N_NOOP( "heavy cream" ), 1053},
+ {I18N_NOOP( "herb stuffing" ), 18082},
+ {I18N_NOOP( "honey" ), 19296},
+ {I18N_NOOP( "horseradish" ), 2055},
+ {I18N_NOOP( "hot pepper flakes" ), 2031},
+ {I18N_NOOP( "hot pepper sauce" ), 6168},
+ {I18N_NOOP( "hot sauce" ), 6164},
+ {I18N_NOOP( "hot water" ), 14429},
+ {I18N_NOOP( "instant dry milk" ), 1155},
+ {I18N_NOOP( "instant rice" ), 20048},
+ {I18N_NOOP( "Italian salad dressing" ), 4114},
+ {I18N_NOOP( "jalapeno peppers" ), 11979},
+ {I18N_NOOP( "juice of 1 lemon" ), 9152},
+ {I18N_NOOP( "ketchup" ), 11935},
+ {I18N_NOOP( "lasagna noodles (8 noodles)" ), 20409},
+ {I18N_NOOP( "leaf lettuce" ), 11253},
+ {I18N_NOOP( "leeks" ), 11246},
+ {I18N_NOOP( "lemon" ), 9150},
+ {I18N_NOOP( "lemon juice" ), 9152},
+ {I18N_NOOP( "lemon juice, bottled" ), 9153},
+ {I18N_NOOP( "lemon juice, frozen" ), 9154},
+ {I18N_NOOP( "lemon peel" ), 9156},
+ {I18N_NOOP( "lemon, sliced" ), 9151},
+ {I18N_NOOP( "lentil" ), 16069},
+ {I18N_NOOP( "lettuce" ), 11253},
+ {I18N_NOOP( "lime juice" ), 9160},
+ {I18N_NOOP( "long-grain white rice" ), 20044},
+ {I18N_NOOP( "low sodium beef broth" ), 6475},
+ {I18N_NOOP( "low sodium chicken broth" ), 6413},
+ {I18N_NOOP( "lowfat 1% milk" ), 1084},
+ {I18N_NOOP( "lowfat cheddar cheese" ), 1168},
+ {I18N_NOOP( "lowfat cottage cheese" ), 1016},
+ {I18N_NOOP( "lowfat mayonnaise" ), 4018},
+ {I18N_NOOP( "lowfat mozzarella cheese" ), 1028},
+ {I18N_NOOP( "lowfat yogurt" ), 1117},
+ {I18N_NOOP( "low-sodium chicken broth" ), 6413},
+ {I18N_NOOP( "low-sodium soy sauce" ), 16125},
+ {I18N_NOOP( "low-sodium vegetable stock" ), 6468},
+ {I18N_NOOP( "margarine" ), 4071},
+ {I18N_NOOP( "margarine, soft" ), 4092},
+ {I18N_NOOP( "marjoram" ), 2023},
+ {I18N_NOOP( "marjoram leaves" ), 2023},
+ {I18N_NOOP( "mayonnaise" ), 4025},
+ {I18N_NOOP( "medium-size egg" ), 1123},
+ {I18N_NOOP( "milk" ), 1081},
+ {I18N_NOOP( "mint leaves" ), 2064},
+ {I18N_NOOP( "mixed fruit in syrup" ), 9099},
+ {I18N_NOOP( "mixed vegetables" ), 11318},
+ {I18N_NOOP( "mixed vegetables, frozen" ), 11583},
+ {I18N_NOOP( "molasses" ), 19304},
+ {I18N_NOOP( "molasses, dark" ), 19305},
+ {I18N_NOOP( "monterey jack cheese" ), 1025},
+ {I18N_NOOP( "mozzarella cheese" ), 1028},
+ {I18N_NOOP( "mung beans" ), 11626},
+ {I18N_NOOP( "mushroom" ), 11260},
+ {I18N_NOOP( "mushrooms" ), 11260},
+ {I18N_NOOP( "mustard" ), 2046},
+ {I18N_NOOP( "mustard seeds" ), 2024},
+ {I18N_NOOP( "navy beans, cooked" ), 16338},
+ {I18N_NOOP( "nonfat dry milk" ), 1092},
+ {I18N_NOOP( "nonfat milk" ), 1085},
+ {I18N_NOOP( "noodles" ), 20409},
+ {I18N_NOOP( "noodles (lasagne)" ), 20409},
+ {I18N_NOOP( "noodles (lasagne) (6-8 bunches)" ), 20409},
+ {I18N_NOOP( "noodles, egg (medium)" ), 20409},
+ {I18N_NOOP( "nutmeg" ), 2025},
+ {I18N_NOOP( "nuts, chopped" ), 16087},
+ {I18N_NOOP( "oil" ), 4518},
+ {I18N_NOOP( "oil, peanut" ), 4042},
+ {I18N_NOOP( "oil, sesame" ), 4058},
+ {I18N_NOOP( "olive oil" ), 4053},
+ {I18N_NOOP( "onion" ), 11282},
+ {I18N_NOOP( "onion, medium" ), 11282},
+ {I18N_NOOP( "onion powder" ), 2026},
+ {I18N_NOOP( "onion salt" ), 2026},
+ {I18N_NOOP( "onion soup" ), 6445},
+ {I18N_NOOP( "onion, chopped" ), 11282},
+ {I18N_NOOP( "onion, large" ), 11282},
+ {I18N_NOOP( "onions" ), 11284},
+ {I18N_NOOP( "onions, chopped" ), 11282},
+ {I18N_NOOP( "orange juice" ), 9206},
+ {I18N_NOOP( "orange juice, from frozen concentra" ), 9215},
+ {I18N_NOOP( "orange rind" ), 9216},
+ {I18N_NOOP( "orange zest" ), 9216},
+ {I18N_NOOP( "oranges" ), 9200},
+ {I18N_NOOP( "oregano" ), 2027},
+ {I18N_NOOP( "Oreo Cookies" ), 18166},
+ {I18N_NOOP( "paprika" ), 2028},
+ {I18N_NOOP( "paprika pepper" ), 2028},
+ {I18N_NOOP( "parmesan cheese" ), 1032},
+ {I18N_NOOP( "parsley" ), 2029},
+ {I18N_NOOP( "parsley flakes" ), 2029},
+ {I18N_NOOP( "parsley stalks" ), 11297},
+ {I18N_NOOP( "parsnips" ), 11298},
+ {I18N_NOOP( "pasta shells" ), 20100},
+ {I18N_NOOP( "peach slices in syrup" ), 9240},
+ {I18N_NOOP( "peanut butter" ), 16098},
+ {I18N_NOOP( "pearled barley" ), 20005},
+ {I18N_NOOP( "peas, canned" ), 11308},
+ {I18N_NOOP( "peas, frozen" ), 11312},
+ {I18N_NOOP( "pecans" ), 12142},
+ {I18N_NOOP( "pepper" ), 2030},
+ {I18N_NOOP( "Pepperidge Farm stuffing" ), 18082},
+ {I18N_NOOP( "pickle relish" ), 11945},
+ {I18N_NOOP( "pickles" ), 11937},
+ {I18N_NOOP( "pimento" ), 11943},
+ {I18N_NOOP( "pine kernels" ), 12149},
+ {I18N_NOOP( "pineapple chunks in juice" ), 9268},
+ {I18N_NOOP( "pineapple juice" ), 9273},
+ {I18N_NOOP( "pinto beans" ), 16042},
+ {I18N_NOOP( "pinto beans, canned" ), 16044},
+ {I18N_NOOP( "plain flour" ), 20081},
+ {I18N_NOOP( "plain low-fat yogurt" ), 1117},
+ {I18N_NOOP( "plain yogurt" ), 1117},
+ {I18N_NOOP( "pork roast" ), 10083},
+ {I18N_NOOP( "potato" ), 11352},
+ {I18N_NOOP( "potato flakes" ), 11379},
+ {I18N_NOOP( "potato Granules" ), 11380},
+ {I18N_NOOP( "potato, canned" ), 11376},
+ {I18N_NOOP( "potatoes" ), 11352},
+ {I18N_NOOP( "potatoes (red-skinned)" ), 11352},
+ {I18N_NOOP( "poultry seasoning" ), 2034},
+ {I18N_NOOP( "powdered sugar" ), 19335},
+ {I18N_NOOP( "processed American cheese" ), 1149},
+ {I18N_NOOP( "provolone cheese" ), 1035},
+ {I18N_NOOP( "prunes" ), 9291},
+ {I18N_NOOP( "pumpkin" ), 11422},
+ {I18N_NOOP( "radishes" ), 11429},
+ {I18N_NOOP( "raisins" ), 9298},
+ {I18N_NOOP( "red burgundy wine" ), 14096},
+ {I18N_NOOP( "red onion" ), 11282},
+ {I18N_NOOP( "red pepper" ), 2031},
+ {I18N_NOOP( "red snapper fillets" ), 15101},
+ {I18N_NOOP( "red wine" ), 14096},
+ {I18N_NOOP( "reduced calorie mayonnaise" ), 4013},
+ {I18N_NOOP( "relish" ), 11945},
+ {I18N_NOOP( "rice" ), 20045},
+ {I18N_NOOP( "ricotta cheese" ), 1037},
+ {I18N_NOOP( "ripe dessert pears" ), 9252},
+ {I18N_NOOP( "rolled oats" ), 20038},
+ {I18N_NOOP( "rosemary" ), 2063},
+ {I18N_NOOP( "rum flavoring or vanilla" ), 2050},
+ {I18N_NOOP( "sage" ), 2038},
+ {I18N_NOOP( "salad dressing (Miracle Whip)" ), 4025},
+ {I18N_NOOP( "salad onions" ), 11282},
+ {I18N_NOOP( "salsa" ), 6164},
+ {I18N_NOOP( "salt" ), 2047},
+ {I18N_NOOP( "sandwich rolls" ), 18349},
+ {I18N_NOOP( "sausage" ), 7063},
+ {I18N_NOOP( "scallions" ), 11291},
+ {I18N_NOOP( "self-raising flour" ), 20082},
+ {I18N_NOOP( "sesame oil" ), 4058},
+ {I18N_NOOP( "shallots" ), 11677},
+ {I18N_NOOP( "shortening" ), 4547},
+ {I18N_NOOP( "shredded carrots" ), 11124},
+ {I18N_NOOP( "shredded lettuce" ), 11252},
+ {I18N_NOOP( "shrimp (raw, medium-size)" ), 15149},
+ {I18N_NOOP( "skim milk" ), 1085},
+ {I18N_NOOP( "skinless boneless chicken breast" ), 5062},
+ {I18N_NOOP( "sliced carrots" ), 11124},
+ {I18N_NOOP( "small mushrooms" ), 11260},
+ {I18N_NOOP( "small onions or shallots" ), 11282},
+ {I18N_NOOP( "smoked bacon" ), 10124},
+ {I18N_NOOP( "soft bread crumbs" ), 18079},
+ {I18N_NOOP( "sour cream" ), 1056},
+ {I18N_NOOP( "soy sauce" ), 16125},
+ {I18N_NOOP( "spaghetti" ), 20120},
+ {I18N_NOOP( "spinach leaf" ), 11457},
+ {I18N_NOOP( "sprig fresh thyme" ), 2049},
+ {I18N_NOOP( "stew beef" ), 13286},
+ {I18N_NOOP( "stock" ), 6413},
+ {I18N_NOOP( "sugar" ), 19335},
+ {I18N_NOOP( "sweet pickle" ), 11940},
+ {I18N_NOOP( "sweet pickle relish" ), 11945},
+ {I18N_NOOP( "sweet potato" ), 11647},
+ {I18N_NOOP( "sweet potato, canned" ), 11645},
+ {I18N_NOOP( "swiss cheese" ), 1040},
+ {I18N_NOOP( "taco sauce" ), 6168},
+ {I18N_NOOP( "taco seasoning mix" ), 2034},
+ {I18N_NOOP( "taco shells" ), 18360},
+ {I18N_NOOP( "taco spice" ), 2034},
+ {I18N_NOOP( "thyme" ), 2042},
+ {I18N_NOOP( "tomato juice" ), 11540},
+ {I18N_NOOP( "tomato paste" ), 11887},
+ {I18N_NOOP( "tomato sauce" ), 11549},
+ {I18N_NOOP( "tomatoes" ), 11529},
+ {I18N_NOOP( "tomatoes, canned" ), 11533},
+ {I18N_NOOP( "tomatoes, stewed" ), 11693},
+ {I18N_NOOP( "tortilla chips" ), 19056},
+ {I18N_NOOP( "tostada shell" ), 18363},
+ {I18N_NOOP( "tuna" ), 15126},
+ {I18N_NOOP( "tuna in water, canned" ), 15126},
+ {I18N_NOOP( "turkey" ), 7079},
+ {I18N_NOOP( "turkey ham" ), 7264},
+ {I18N_NOOP( "turmeric" ), 2043},
+ {I18N_NOOP( "turnip" ), 11564},
+ {I18N_NOOP( "unflavored gelatin" ), 19173},
+ {I18N_NOOP( "vanilla" ), 2050},
+ {I18N_NOOP( "vanilla extract" ), 2050},
+ {I18N_NOOP( "vanilla pudding mix (instant)" ), 19202},
+ {I18N_NOOP( "veal shank" ), 17278},
+ {I18N_NOOP( "vegetable oil" ), 4518},
+ {I18N_NOOP( "vegetable stock" ), 6468},
+ {I18N_NOOP( "Velveeta" ), 1149},
+ {I18N_NOOP( "vinegar" ), 2048},
+ {I18N_NOOP( "walnuts" ), 12154},
+ {I18N_NOOP( "warm water" ), 14429},
+ {I18N_NOOP( "water" ), 14429},
+ {I18N_NOOP( "whipped topping" ), 1054},
+ {I18N_NOOP( "white flour" ), 20481},
+ {I18N_NOOP( "white pepper" ), 2032},
+ {I18N_NOOP( "white rice" ), 20050},
+ {I18N_NOOP( "white turnips" ), 11564},
+ {I18N_NOOP( "white vinegar" ), 2048},
+ {I18N_NOOP( "white wine" ), 14106},
+ {I18N_NOOP( "whole wheat flour" ), 20080},
+ {I18N_NOOP( "yeast" ), 18375},
+ {I18N_NOOP( "yellow squash" ), 11641},
+ {I18N_NOOP( "zucchini" ), 11477},
+ {I18N_NOOP( "zucchini slices" ), 11953},
+ {0, 0}
+ };
+
+#endif //USDA_INGREDIENT_DATA_H
diff --git a/krecipes/src/backends/usda_property_data.h b/krecipes/src/backends/usda_property_data.h
new file mode 100644
index 0000000..a0ae784
--- /dev/null
+++ b/krecipes/src/backends/usda_property_data.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDA_PROPERTY_DATA_H
+#define USDA_PROPERTY_DATA_H
+
+#include <klocale.h>
+
+struct property_data
+{
+ int id;
+ QString name;
+ const char *unit;
+};
+
+#define TOTAL_USDA_PROPERTIES 43
+
+//NOTE: the following must be in this order
+static property_data property_data_list[] = {
+ { -1, I18N_NOOP( "water" ), "g"},
+ { -1, I18N_NOOP( "energy" ), "kcal"},
+ { -1, I18N_NOOP( "protein" ), "g"},
+ { -1, I18N_NOOP( "fat" ), "g"},
+ { -1, I18N_NOOP( "ash" ), "g"},
+ { -1, I18N_NOOP( "carbohydrates" ), "g"},
+ { -1, I18N_NOOP( "dietary fiber" ), "g"},
+ { -1, I18N_NOOP( "sugar" ), "g"},
+ { -1, I18N_NOOP( "calcuim" ), "mg"},
+ { -1, I18N_NOOP( "iron" ), "mg"},
+ { -1, I18N_NOOP( "magnesium" ), "mg"},
+ { -1, I18N_NOOP( "phosphorus" ), "mg"},
+ { -1, I18N_NOOP( "potassium" ), "mg"},
+ { -1, I18N_NOOP( "sodium" ), "mg"},
+ { -1, I18N_NOOP( "zinc" ), "mg"},
+ { -1, I18N_NOOP( "copper" ), "mg"},
+ { -1, I18N_NOOP( "manganese" ), "mg"},
+ { -1, I18N_NOOP( "selenium" ), "g"},
+ { -1, I18N_NOOP( "vitamin C" ), "mg"},
+ { -1, I18N_NOOP( "thiamin" ), "mg"},
+ { -1, I18N_NOOP( "riboflavin" ), "mg"},
+ { -1, I18N_NOOP( "niacin" ), "mg"},
+ { -1, I18N_NOOP( "pantothenic acid" ), "mg"},
+ { -1, I18N_NOOP( "vitamin B" ), "mg"},
+ { -1, I18N_NOOP( "folate" ), "g"},
+ { -1, I18N_NOOP( "folic acid" ), "g"},
+ { -1, I18N_NOOP( "food folate" ), "g"},
+ { -1, I18N_NOOP( "folate (DFE)" ), "g"},
+ { -1, I18N_NOOP( "vitamin B12" ), "g"},
+ { -1, I18N_NOOP( "vitamin A" ), "IU"},
+ { -1, I18N_NOOP( "vitamin A (RAE)" ), "mg"},
+ { -1, I18N_NOOP( "retinol" ), "g"},
+ { -1, I18N_NOOP( "vitamin E" ), "g"},
+ { -1, I18N_NOOP( "vitamin K" ), "g"},
+ { -1, I18N_NOOP( "alpha-carotene" ), "g"},
+ { -1, I18N_NOOP( "beta-carotene" ), "g"},
+ { -1, I18N_NOOP( "beta-cryptoxanthin" ), "g"},
+ { -1, I18N_NOOP( "lycopene" ), "g"},
+ { -1, I18N_NOOP( "lutein+zeazanthin" ), "g"},
+ { -1, I18N_NOOP( "saturated fat" ), "g"},
+ { -1, I18N_NOOP( "monounsaturated fat" ), "g"},
+ { -1, I18N_NOOP( "polyunsaturated fat" ), "g"},
+ { -1, I18N_NOOP( "cholesterol" ), "mg"},
+ { 0, 0, 0 }
+ };
+
+#endif //USDA_PROPERTY_DATA_H
diff --git a/krecipes/src/backends/usda_unit_data.h b/krecipes/src/backends/usda_unit_data.h
new file mode 100644
index 0000000..2c3a5ef
--- /dev/null
+++ b/krecipes/src/backends/usda_unit_data.h
@@ -0,0 +1,104 @@
+/***************************************************************************
+* Copyright (C) 2006 *
+* *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDA_UNIT_DATA_H
+#define USDA_UNIT_DATA_H
+
+#include <klocale.h>
+
+#include <qstring.h>
+
+struct unit_data {
+ const char *name;
+ const char *plural;
+};
+
+static unit_data unit_data_list[] = {
+ {"bag","bags"},
+ {"block","blocks"},
+ {"bottle","bottles"},
+ {"box","boxes"},
+ {"bunch","bunches"},
+ {"can","cans"},
+ {"cone","cones"},
+ {"container","containers"},
+ {"cube","cubes"},
+ {"cup","cups"},
+ {"fl oz","fl oz"},
+ {"glass","glasses"},
+ {"item","items"},
+ {"loaf","loaves"},
+ {"large","large"},
+ {"lb","lbs"},
+ {"junior","junior"},
+ {"leaf","leaves"},
+ {"medium","medium"},
+ {"oz","oz"},
+ {"pack","packs"},
+ {"package","packages"},
+ {"packet","packets"},
+ {"piece","pieces"},
+ {"pouch","pouches"},
+ {"quart","quarts"},
+ {"scoop","scoops"},
+ {"sheet","sheets"},
+ {"slice","slices"},
+ {"small","small"},
+ {"spear","spears"},
+ {"sprout","spouts"},
+ {"sprig","sprigs"},
+ {"square","squares"},
+ {"stalk","stalks"},
+ {"stem","stems"},
+ {"strip","strips"},
+ {"tablespoon","tablespoons"},
+ {"tbsp","tbsp"},
+ {"teaspoon","teaspoons"},
+ {"tsp","tsp"},
+ {"tube","tubes"},
+ {"unit","units"},
+ {0,0}
+};
+
+static const char * prep_data_list[] = {
+ "chopped",
+ "diced",
+ "sliced",
+ "crumbled",
+ "crushed",
+ "ground",
+ "grated",
+ "mashed",
+ "melted",
+ "packed",
+ "pureed",
+ "quartered",
+ "thawed",
+ "shredded",
+ "sifted",
+ "pared",
+ "flaked",
+ "unpacked",
+ "unsifted",
+ "unthawed",
+ "pitted",
+ "peeled",
+ "cooked",
+ "hulled",
+ "shelled",
+ "raw",
+ "whipped",
+ 0
+};
+
+bool parseUSDAUnitAndPrep( const QString &string, QString &unit, QString &prep );
+
+#endif //USDA_UNIT_DATA_H
diff --git a/krecipes/src/convert_sqlite3.cpp b/krecipes/src/convert_sqlite3.cpp
new file mode 100644
index 0000000..19de813
--- /dev/null
+++ b/krecipes/src/convert_sqlite3.cpp
@@ -0,0 +1,121 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "convert_sqlite3.h"
+
+#include <qapplication.h>
+#include <qfile.h>
+
+#include <kdebug.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <kprocio.h>
+
+//FIXME: Some messages should be given to the user about success/failure, but that can't be done in the 0.8.x branch due to i18n.
+
+ConvertSQLite3::ConvertSQLite3( const QString &db_file ) : QObject(), error(false)
+{
+ QString file = db_file;
+ if ( file.isEmpty() ) {
+ KConfig *config = KGlobal::config();
+ config->setGroup("Server");
+ file = config->readEntry("DBFile");
+ }
+
+ KProcIO *p = new KProcIO;
+ p->setUseShell(true);
+
+ //sqlite OLD.DB .dump | sqlite3 NEW.DB
+ *p << "sqlite" << file << ".dump" <<
+ "|" << "sqlite3" << file+".new";
+
+ QApplication::connect( p, SIGNAL(readReady(KProcIO*)), this, SLOT(processOutput(KProcIO*)) );
+
+ bool success = p->start( KProcess::Block, true );
+ if ( !success ) {
+ kdDebug()<<"Conversion failed... unable to start KProcess"<<endl;
+ return;
+ }
+
+ if ( error )
+ return;
+
+
+ QString backup_file = file+".sqlite2";
+ int i = 1;
+ while ( QFile::exists(backup_file) ) {
+ backup_file = backup_file.left(file.length()+8)+"."+QString::number(i);
+ ++i;
+ }
+
+ if ( !copyFile( file, backup_file ) ) {
+ kdDebug()<<"Unable to backup SQLite 2 database... aborting"<<endl
+ <<"A successfully converted SQLite 3 file is available at \""<<file<<".new\"."<<endl;
+ }
+ else {
+ kdDebug()<<"SQLite 2 database backed up to "<<backup_file<<endl;
+ if ( !copyFile( file+".new", file ) ) {
+ kdDebug()<<"Unable to copy the new SQLite 3 database to: "<<file<<"."<<endl
+ <<"You may manually move \""<<file<<".new\" to \""<<file<<"\""<<endl;
+ }
+ else {
+ kdDebug()<<"Conversion successful!"<<endl;
+ QFile::remove(file+".new");
+ }
+ }
+}
+
+ConvertSQLite3::~ConvertSQLite3()
+{
+}
+
+void ConvertSQLite3::processOutput( KProcIO* p )
+{
+ QString error_str, buffer;
+ while ( p->readln(buffer) != -1 ) {
+ error_str += buffer;
+ }
+
+ KMessageBox::error( 0, error_str );
+
+ error = true;
+
+ p->ackRead();
+}
+
+bool ConvertSQLite3::copyFile( const QString &oldFilePath, const QString &newFilePath )
+{
+ //load both files
+ QFile oldFile(oldFilePath);
+ QFile newFile(newFilePath);
+ bool openOld = oldFile.open( IO_ReadOnly );
+ bool openNew = newFile.open( IO_WriteOnly );
+
+ //if either file fails to open bail
+ if(!openOld || !openNew) { return false; }
+
+ //copy contents
+ uint BUFFER_SIZE = 16000;
+ char* buffer = new char[BUFFER_SIZE];
+ while(!oldFile.atEnd())
+ {
+ Q_ULONG len = oldFile.readBlock( buffer, BUFFER_SIZE );
+ newFile.writeBlock( buffer, len );
+ }
+
+ //deallocate buffer
+ delete[] buffer;
+ buffer = NULL;
+ return true;
+}
+
+#include "convert_sqlite3.moc"
+
diff --git a/krecipes/src/convert_sqlite3.h b/krecipes/src/convert_sqlite3.h
new file mode 100644
index 0000000..8387464
--- /dev/null
+++ b/krecipes/src/convert_sqlite3.h
@@ -0,0 +1,35 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CONVERT_SQLITE3_H
+#define CONVERT_SQLITE3_H
+
+#include <qobject.h>
+
+class KProcIO;
+
+class ConvertSQLite3 : public QObject
+{
+Q_OBJECT
+
+public:
+ ConvertSQLite3( const QString &db_file = QString::null );
+ ~ConvertSQLite3();
+
+public slots:
+ void processOutput( KProcIO* p );
+
+private:
+ bool copyFile( const QString &oldFilePath, const QString &newFilePath );
+
+ bool error;
+};
+
+#endif //CONVERT_SQLITE3_H
diff --git a/krecipes/src/datablocks/Makefile.am b/krecipes/src/datablocks/Makefile.am
new file mode 100644
index 0000000..1dea3ec
--- /dev/null
+++ b/krecipes/src/datablocks/Makefile.am
@@ -0,0 +1,20 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir)/.. $(all_includes)
+
+noinst_LTLIBRARIES=libdatablocks.la
+libdatablocks_la_SOURCES= \
+ recipelist.cpp constraintlist.cpp categorytree.cpp kreborder.cpp \
+ recipe.cpp ingredient.cpp ingredientlist.cpp elementlist.cpp \
+ element.cpp ingredientproperty.cpp ingredientpropertylist.cpp \
+ unit.cpp unitratio.cpp unitratiolist.cpp mixednumber.cpp rating.cpp \
+ weight.cpp
+
+libdatablocks_la_METASOURCES=AUTO
+
+#the library search path.
+libdatablocks_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/datablocks/categorytree.cpp b/krecipes/src/datablocks/categorytree.cpp
new file mode 100644
index 0000000..bb61de6
--- /dev/null
+++ b/krecipes/src/datablocks/categorytree.cpp
@@ -0,0 +1,133 @@
+/***************************************************************************
+* Copyright (C) 2004 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "categorytree.h"
+
+#include <kdebug.h>
+
+#include "element.h"
+
+CategoryTree::CategoryTree( CategoryTree *parent ) :
+ m_parent( 0 ), m_child( 0 ), m_sibling( 0 ), m_last(0), m_count(0)
+{
+ if ( parent )
+ parent->insertItem( this );
+}
+
+CategoryTree::~CategoryTree()
+{
+ if ( m_parent )
+ m_parent->takeItem( this );
+
+ CategoryTree * i = m_child;
+ m_child = 0;
+ while ( i ) {
+ i->m_parent = 0;
+ CategoryTree * n = i->m_sibling;
+ delete i;
+ i = n;
+ }
+}
+
+CategoryTree *CategoryTree::add
+ ( const Element &cat )
+{
+ CategoryTree * new_child = new CategoryTree( this );
+ new_child->category = cat;
+
+ m_count++;
+
+ return new_child;
+}
+
+void CategoryTree::insertItem( CategoryTree *newChild )
+{
+ newChild->m_parent = this;
+ if ( m_child && m_child->m_last )
+ m_child->m_last->m_sibling = newChild;
+ else
+ m_child = newChild;
+
+ m_child->m_last = newChild;
+ m_count++;
+}
+
+void CategoryTree::takeItem( CategoryTree *tree )
+{
+ kdError()<< "Both these methods seem to be broken... don't use this function!" << endl;
+
+ CategoryTree *lastItem = m_child->m_last;
+#if 0
+ CategoryTree ** nextChild = &m_child;
+ while( nextChild && *nextChild && tree != *nextChild )
+ nextChild = &((*nextChild)->m_sibling);
+
+ if ( nextChild && tree == *nextChild ) {
+ *nextChild = (*nextChild)->m_sibling;
+ }
+ tree->m_parent = 0;
+ tree->m_sibling = 0;
+#else
+ for ( CategoryTree *it = firstChild(); it; it = it->nextSibling() ) {
+ if ( it->nextSibling() == tree ) {
+ it->m_sibling = tree->nextSibling();
+ break;
+ }
+ }
+ tree->m_parent = 0;
+ tree->m_sibling = 0;
+#endif
+ if ( tree != m_last )
+ m_child->m_last = lastItem;
+ else //FIXME: unstable behavior if this is the case
+ kdDebug()<<"CategoryTree::takeItem: warning - unstable behavior expected"<<endl;
+ m_count--;
+}
+
+void CategoryTree::clear()
+{
+ CategoryTree *c = m_child;
+ CategoryTree *n;
+ while( c ) {
+ n = c->m_sibling;
+ delete c;
+ c = n;
+ }
+}
+
+bool CategoryTree::contains( int id ) const
+{
+ bool result = false;
+
+ for ( CategoryTree * child_it = firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ if ( child_it->category.id == id )
+ return true;
+
+ result = child_it->contains( id );
+ }
+
+ return result;
+}
+
+CategoryTree* CategoryTree::find( int id ) const
+{
+ CategoryTree *result = 0;
+
+ for ( CategoryTree * child_it = firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ if ( child_it->category.id == id )
+ return child_it;
+
+ result = child_it->find( id );
+ }
+
+ return result;
+}
+
+
diff --git a/krecipes/src/datablocks/categorytree.h b/krecipes/src/datablocks/categorytree.h
new file mode 100644
index 0000000..e5668e4
--- /dev/null
+++ b/krecipes/src/datablocks/categorytree.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+* Copyright (C) 2004 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef CATEGORYTREE_H
+#define CATEGORYTREE_H
+
+#include "element.h"
+
+class CategoryTree
+{
+public:
+ CategoryTree( CategoryTree *parent = 0 );
+
+ ~CategoryTree();
+
+ Element category;
+
+ CategoryTree *add
+ ( const Element &cat );
+ void clear();
+
+ bool contains( int id ) const;
+ CategoryTree* find( int id ) const;
+
+ CategoryTree *parent() const
+ {
+ return m_parent;
+ }
+ CategoryTree *firstChild() const
+ {
+ return m_child;
+ }
+ CategoryTree *nextSibling() const
+ {
+ return m_sibling;
+ }
+
+ void takeItem( CategoryTree * );
+ void insertItem( CategoryTree * );
+
+ int count() const { return m_count; }
+
+private:
+ CategoryTree( const CategoryTree & );
+ CategoryTree &operator=( const CategoryTree & );
+
+ CategoryTree *m_parent;
+ CategoryTree *m_child;
+ CategoryTree *m_sibling;
+ CategoryTree *m_last;
+
+ int m_count;
+};
+
+#endif
diff --git a/krecipes/src/datablocks/constraintlist.cpp b/krecipes/src/datablocks/constraintlist.cpp
new file mode 100644
index 0000000..f96ffc9
--- /dev/null
+++ b/krecipes/src/datablocks/constraintlist.cpp
@@ -0,0 +1,11 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "constraintlist.h"
+
diff --git a/krecipes/src/datablocks/constraintlist.h b/krecipes/src/datablocks/constraintlist.h
new file mode 100644
index 0000000..4269725
--- /dev/null
+++ b/krecipes/src/datablocks/constraintlist.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef CONSTRAINTLIST_H
+#define CONSTRAINTLIST_H
+
+#include <qstring.h>
+
+#include "unit.h"
+
+/**
+@author Unai Garro
+*/
+
+class IngredientProperty;
+
+class Constraint
+{
+public:
+ Constraint()
+ {
+ max = 0.0;
+ min = 0.0;
+ id = -1;
+ enabled = false;
+ }
+
+ ~Constraint(){}
+
+ int id;
+ QString name;
+ QString units;
+ Unit perUnit; // stores the unit ID, name, and type
+ double max;
+ double min;
+ bool enabled;
+};
+
+typedef QValueList< Constraint > ConstraintList;
+
+#endif
diff --git a/krecipes/src/datablocks/element.cpp b/krecipes/src/datablocks/element.cpp
new file mode 100644
index 0000000..786fb0b
--- /dev/null
+++ b/krecipes/src/datablocks/element.cpp
@@ -0,0 +1,43 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "element.h"
+
+Element::Element() :
+ id( -1 )
+{}
+
+Element::Element( const QString &_name, int _id ) :
+ name( _name ),
+ id( _id )
+{}
+
+Element::Element( const Element &el )
+{
+ id = el.id;
+ name = el.name;
+}
+
+Element::~Element()
+{}
+
+Element& Element::operator=( const Element &el )
+{
+ id = el.id;
+ name = el.name;
+ return *this;
+}
+
+bool Element::operator==( const Element &el ) const
+{
+ return ( el.id == id );
+}
diff --git a/krecipes/src/datablocks/element.h b/krecipes/src/datablocks/element.h
new file mode 100644
index 0000000..4906fcf
--- /dev/null
+++ b/krecipes/src/datablocks/element.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef ELEMENT_H
+#define ELEMENT_H
+#include <qstring.h>
+/**
+@author Unai Garro
+*/
+class Element
+{
+public:
+ Element();
+ Element( const QString &name, int id = -1 );
+ Element( const Element &el );
+
+ ~Element();
+ QString name;
+ int id;
+ Element& operator=( const Element &el );
+
+ /** Compare two elements by their id */
+ bool operator==( const Element & ) const;
+
+ /** Compare (sort) by name */
+ bool operator<( const Element &e ) const
+ {
+ return name < e.name;
+ }
+};
+
+#endif
diff --git a/krecipes/src/datablocks/elementlist.cpp b/krecipes/src/datablocks/elementlist.cpp
new file mode 100644
index 0000000..321e1ea
--- /dev/null
+++ b/krecipes/src/datablocks/elementlist.cpp
@@ -0,0 +1,102 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "datablocks/elementlist.h"
+
+ElementList::ElementList() : QValueList <Element>()
+{}
+
+ElementList::~ElementList()
+{}
+
+Element ElementList::getElement( int index ) const
+{
+ return * ( at( index ) );
+}
+
+Element ElementList::findByName( const QString &name ) const
+{
+ ElementList::const_iterator it_end = end();
+ for ( ElementList::const_iterator it = begin(); it != it_end; ++it ) {
+ if ( ( *it ).name == name )
+ return * it;
+ }
+
+ Element el;
+ el.id = -1;
+ return el;
+}
+
+Element ElementList::findByName( const QRegExp &rx ) const
+{
+ ElementList::const_iterator it_end = end();
+ for ( ElementList::const_iterator it = begin(); it != it_end; ++it ) {
+ if ( ( *it ).name.find(rx) != -1 )
+ return * it;
+ }
+
+ Element el;
+ el.id = -1;
+ return el;
+}
+
+bool ElementList::containsId( int id ) const // Search by id (which uses search by item, with comparison defined on header)
+{
+ if ( id == -1 ) {
+ return count() == 0;
+ }
+
+ Element i;
+ i.id = id;
+ return ( find( i ) != end() );
+}
+
+bool ElementList::containsSubSet( ElementList &el )
+{
+ ElementList::const_iterator it_end = el.end();
+ ElementList::const_iterator it;
+
+ for ( it = el.begin(); it != it_end; ++it ) {
+ if ( !containsId( ( *it ).id ) )
+ return false;
+ }
+ return true;
+}
+
+QString ElementList::join( const QString &sep ) const
+{
+ QString ret;
+
+ ElementList::const_iterator it_end = end();
+ ElementList::const_iterator it;
+
+ for ( it = begin(); it != it_end; ++it ) {
+ if ( it != begin() )
+ ret += sep;
+ ret += (*it).name;
+ }
+
+ return ret;
+}
+
+ElementList ElementList::split( const QString &sep, const QString &str )
+{
+ ElementList ret;
+ QStringList list = QStringList::split(sep,str);
+
+ QStringList::const_iterator it_end = list.end();
+ QStringList::const_iterator it;
+
+ for ( it = list.begin(); it != it_end; ++it ) {
+ ret.append( Element((*it).stripWhiteSpace()) );
+ }
+
+ return ret;
+} \ No newline at end of file
diff --git a/krecipes/src/datablocks/elementlist.h b/krecipes/src/datablocks/elementlist.h
new file mode 100644
index 0000000..1a4f369
--- /dev/null
+++ b/krecipes/src/datablocks/elementlist.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef ELEMENTLIST_H
+#define ELEMENTLIST_H
+
+#include <qvaluelist.h>
+#include <qregexp.h>
+
+#include "element.h"
+
+/**
+@author Unai Garro
+*/
+class ElementList: public QValueList<Element>
+{
+public:
+ ElementList();
+ ~ElementList();
+
+ bool containsId( int id ) const;
+ bool containsSubSet( ElementList &el );
+
+ Element findByName( const QString & ) const;
+ Element findByName( const QRegExp & ) const;
+
+ Element getElement( int index ) const;
+
+ QString join( const QString &sep ) const;
+
+ static ElementList split( const QString &sep, const QString &str );
+};
+
+typedef QValueList<int> IDList;
+
+#endif
diff --git a/krecipes/src/datablocks/ingredient.cpp b/krecipes/src/datablocks/ingredient.cpp
new file mode 100644
index 0000000..a078f84
--- /dev/null
+++ b/krecipes/src/datablocks/ingredient.cpp
@@ -0,0 +1,95 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "datablocks/ingredient.h"
+
+#include <qstringlist.h>
+
+#include "mixednumber.h"
+
+IngredientData::IngredientData() : amount( 0 ), amount_offset( 0 ), groupID( -1 )
+{}
+
+IngredientData::IngredientData( const QString &_name, double _amount, const Unit &_units, int _unitID, int _ingredientID ) :
+ ingredientID( _ingredientID ),
+ name( _name ),
+ amount( _amount ),
+ amount_offset( 0 ),
+ units( _units ),
+ groupID( -1 )
+{
+units.id=_unitID;
+}
+
+//compare also using the group id because there may be the same ingredient in a list multiple times, but each in a different group
+bool IngredientData::operator==( const IngredientData &ing ) const
+{
+ return ( ( ing.ingredientID == ingredientID ) && ( ing.groupID == groupID ) );
+}
+
+Ingredient::Ingredient() : IngredientData()
+{}
+
+Ingredient::Ingredient( const QString &_name, double _amount, const Unit &_units, int _unitID, int _ingredientID ) :
+ IngredientData(_name,_amount,_units,_unitID,_ingredientID)
+{}
+
+Ingredient::Ingredient( const IngredientData &d ) : IngredientData(d)
+{}
+
+void Ingredient::setAmount( const QString &range, bool *ok )
+{
+ if ( range.isEmpty() ) {
+ if ( ok ) *ok = true;
+ return;
+ }
+
+ QStringList ranges = QStringList::split('-',range);
+
+ QString amount_min = ranges[0];
+ if ( amount_min.isEmpty() ) {
+ if ( ok ) *ok = false;
+ return;
+ }
+
+ QString amount_max;
+ switch ( ranges.count() ) {
+ case 0:
+ case 1: break;
+ case 2: amount_max = ranges[1];
+ break;
+ default:
+ if ( ok ) *ok = false;
+ return;
+ }
+
+ MixedNumber nm_min = MixedNumber::fromString( amount_min, ok );
+ if ( ok && *ok == false ) return;
+
+ MixedNumber nm_max = MixedNumber::fromString( amount_max, ok );
+ if ( ok && *ok == false ) return;
+
+ amount = nm_min.toDouble();
+ if ( nm_max > 0 )
+ amount_offset = nm_max.toDouble() - amount;
+}
+
+//compare also using the group id because there may be the same ingredient in a list multiple times, but each in a different group
+bool Ingredient::operator==( const Ingredient &ing ) const
+{
+ return ( ( ing.ingredientID == ingredientID ) && ( ing.groupID == groupID ) );
+}
+
+bool Ingredient::operator<( const Ingredient &ing ) const
+{
+ return ( QString::localeAwareCompare( name, ing.name ) < 0 );
+}
diff --git a/krecipes/src/datablocks/ingredient.h b/krecipes/src/datablocks/ingredient.h
new file mode 100644
index 0000000..8bcf28f
--- /dev/null
+++ b/krecipes/src/datablocks/ingredient.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* Copyright (C) 2006 Jason Kivlighn <jkivlighn@gmail.com> *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef INGREDIENT_H
+#define INGREDIENT_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#include "datablocks/unit.h"
+#include "datablocks/elementlist.h"
+
+//###: Is there a better way to get the behavior of a list of Ingredient
+// objects as a data member of Ingredient?
+class IngredientData
+{
+public:
+ IngredientData();
+ IngredientData( const QString &name, double amount, const Unit &units, int unitID = -1, int ingredientID = -1 );
+
+ int ingredientID;
+ QString name;
+ double amount;
+ double amount_offset;
+ Unit units;
+ int groupID;
+ QString group;
+ ElementList prepMethodList;
+
+ /** Compare two elements by their id */
+ bool operator==( const IngredientData & ) const;
+};
+
+/**
+@author Unai Garro
+*/
+class Ingredient : public IngredientData
+{
+public:
+ Ingredient();
+ Ingredient( const QString &name, double amount, const Unit &units, int unitID = -1, int ingredientID = -1 );
+ Ingredient( const IngredientData& );
+
+ QValueList<IngredientData> substitutes;
+
+ void setAmount( const QString &range, bool *ok = 0 );
+
+ /** Compare two elements by their id and groupID */
+ bool operator==( const Ingredient & ) const;
+
+ /** This is used for sorting, and so we compare by name */
+ bool operator<( const Ingredient & ) const;
+};
+
+#endif
diff --git a/krecipes/src/datablocks/ingredientlist.cpp b/krecipes/src/datablocks/ingredientlist.cpp
new file mode 100644
index 0000000..c522a46
--- /dev/null
+++ b/krecipes/src/datablocks/ingredientlist.cpp
@@ -0,0 +1,272 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "ingredientlist.h"
+
+#include "backends/recipedb.h"
+
+IngredientList::IngredientList() : QValueList<Ingredient>()
+{}
+
+IngredientList::~IngredientList()
+{}
+
+bool IngredientList::contains( const Ingredient &ing, bool compareAmount, RecipeDB *database ) const
+{
+ bool ret = false;
+ for ( IngredientList::const_iterator it = begin(); it != end(); ++it ) {
+ if ( (*it).ingredientID == ing.ingredientID ) {
+ //see if we have enough of the ingredient
+ //(only if an amount was specified for both)
+ if ( compareAmount && (*it).amount > 0 && ing.amount > 0 ) {
+ Ingredient convertedIng;
+ if ( database->convertIngredientUnits( ing, (*it).units, convertedIng ) )
+ ret = (*it).amount >= convertedIng.amount;
+ else //we couldn't do the conversion
+ ret = true;
+ }
+ else
+ ret = true;
+ }
+ }
+ if ( !ret ) {
+ for ( QValueList<IngredientData>::const_iterator it = ing.substitutes.begin(); it != ing.substitutes.end(); ++it ) {
+ ret = contains(*it, compareAmount, database);
+ if ( ret ) break;
+ }
+ }
+ return ret;
+}
+
+bool IngredientList::containsSubSet( IngredientList &il, IngredientList &missing, bool compareAmount, RecipeDB *database )
+{
+ missing.empty();
+ bool contained = true;
+ IngredientList::const_iterator it;
+
+ for ( it = il.begin();it != il.end();++it ) {
+ if ( !contains( *it, compareAmount, database ) ) {
+ contained = false;
+ missing.append( *it );
+ }
+ }
+
+ return contained;
+}
+
+bool IngredientList::containsSubSet( IngredientList &il ) const
+{
+ IngredientList::const_iterator it;
+
+ for ( it = il.begin();it != il.end();++it ) {
+ if ( !contains( *it ) ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IngredientList::containsAny( const IngredientList &list, bool compareAmount, RecipeDB *database )
+{
+ for ( IngredientList::const_iterator this_list_it = begin(); this_list_it != end(); ++this_list_it ) {
+ for ( IngredientList::const_iterator contains_list_it = list.begin(); contains_list_it != list.end(); ++contains_list_it ) {
+ if ( contains( *contains_list_it, compareAmount, database ) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void IngredientList::empty( void )
+{
+ clear();
+}
+
+int IngredientList::find( int id ) const // Search by id (which uses search by item, with comparison defined on header)
+{
+ Ingredient i;
+ i.ingredientID = id;
+ return findIndex( i );
+}
+
+Ingredient IngredientList::findByName( const QString &ing ) const
+{
+ IngredientList::const_iterator it_end = end();
+ for ( IngredientList::const_iterator it = begin(); it != it_end; ++it ) {
+ if ( ( *it ).name == ing )
+ return *it;
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = ( *it ).substitutes.begin(); sub_it != ( *it ).substitutes.end(); ++sub_it ) {
+ if ( ( *sub_it ).name == ing )
+ return *sub_it;
+ }
+ }
+
+ Ingredient el;
+ el.ingredientID = -1;
+ return el;
+}
+
+Ingredient IngredientList::findByName( const QRegExp &rx ) const
+{
+ IngredientList::const_iterator it_end = end();
+ for ( IngredientList::const_iterator it = begin(); it != it_end; ++it ) {
+ if ( ( *it ).name.find(rx) != -1 )
+ return *it;
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = ( *it ).substitutes.begin(); sub_it != ( *it ).substitutes.end(); ++sub_it ) {
+ if ( ( *sub_it ).name.find(rx) != -1 )
+ return *sub_it;
+ }
+ }
+
+ Ingredient el;
+ el.ingredientID = -1;
+ return el;
+}
+
+IngredientList::const_iterator IngredientList::find( IngredientList::const_iterator it, int id ) const // Search by id (which uses search by item, with comparison defined on header)
+{
+ Ingredient i;
+ i.ingredientID = id;
+ return QValueList<Ingredient>::find( it, i );
+}
+
+IngredientList::iterator IngredientList::find( IngredientList::iterator it, int id ) // Search by id (which uses search by item, with comparison defined on header)
+{
+ Ingredient i;
+ i.ingredientID = id;
+ return QValueList<Ingredient>::find( it, i );
+}
+
+IngredientData& IngredientList::findSubstitute( const Ingredient &i ) // Search by id (which uses search by item, with comparison defined on header)
+{
+ QValueList<Ingredient>::iterator result = QValueList<Ingredient>::find(i);
+ if ( result == end() ) {
+ for ( IngredientList::iterator it = begin(); it != end(); ++it ) {
+ QValueList<IngredientData>::iterator result = (*it).substitutes.find(i);
+ if ( result != (*it).substitutes.end() )
+ return *result;
+ }
+ }
+ return *result;
+}
+
+void IngredientList::removeSubstitute( const Ingredient &i )
+{
+ QValueList<Ingredient>::iterator result = QValueList<Ingredient>::find(i);
+ if ( result == end() ) {
+ for ( IngredientList::iterator it = begin(); it != end(); ++it ) {
+ QValueList<IngredientData>::iterator result = (*it).substitutes.find(i);
+ if ( result != (*it).substitutes.end() ) {
+ (*it).substitutes.remove(result);
+ return;
+ }
+ }
+ }
+ remove(result);
+}
+
+void IngredientList::move( int index1, int index2 ) //moves element in pos index1, to pos after index2
+{
+ IngredientList::iterator tmp_it = at( index1 );
+ Ingredient tmp_ing( *tmp_it );
+
+ remove
+ ( tmp_it );
+
+ tmp_it = at( index2 );
+ insert( tmp_it, tmp_ing );
+}
+
+void IngredientList::move( int index1, int count1, int index2 ) //moves element in pos index1 and the following count1 items, to pos after index2
+{
+ IngredientList::iterator tmp_it = at( index1 );
+ IngredientList::iterator after_it = at( index2 + 1 );
+
+ for ( int i = 0; i < count1; ++i )
+ {
+ Ingredient tmp_ing( *tmp_it );
+
+ IngredientList::iterator remove_me_it = tmp_it;
+ ++tmp_it;
+ remove
+ ( remove_me_it );
+
+ insert( after_it, tmp_ing );
+ }
+}
+
+IngredientList IngredientList::groupMembers( int id, IngredientList::const_iterator begin ) const
+{
+ bool first_found = false;
+
+ IngredientList matches;
+ for ( IngredientList::const_iterator it = begin; it != end(); ++it ) {
+ if ( ( *it ).groupID == id ) {
+ matches.append( *it );
+ first_found = true;
+ }
+ else if ( first_found ) //this is the end of this group... there may be more later though
+ break;
+ }
+
+ return matches;
+}
+
+QValueList<IngredientList::const_iterator> IngredientList::_groupMembers( int id, IngredientList::const_iterator begin ) const
+{
+ bool first_found = false;
+
+ QValueList<IngredientList::const_iterator> matches;
+ for ( IngredientList::const_iterator it = begin; it != end(); ++it ) {
+ if ( ( *it ).groupID == id ) {
+ matches << it;
+ first_found = true;
+ }
+ else if ( first_found ) //this is the end of this group... there may be more later though
+ break;
+ }
+
+ return matches;
+}
+
+IngredientList IngredientList::firstGroup()
+{
+ usedGroups.clear();
+
+ QValueList<IngredientList::const_iterator> members = _groupMembers( ( *begin() ).groupID, begin() );
+
+ for ( QValueList<IngredientList::const_iterator>::const_iterator members_it = members.begin(); members_it != members.end(); ++members_it ) {
+ usedGroups << *members_it;
+ }
+
+ return groupMembers( ( *begin() ).groupID, begin() );
+}
+
+IngredientList IngredientList::nextGroup()
+{
+ for ( IngredientList::const_iterator it = begin(); it != end(); ++it ) {
+ if ( usedGroups.find( it ) == usedGroups.end() ) {
+ QValueList<IngredientList::const_iterator> members = _groupMembers( ( *it ).groupID, it );
+
+ for ( QValueList<IngredientList::const_iterator>::const_iterator members_it = members.begin(); members_it != members.end(); ++members_it ) {
+ usedGroups << *members_it;
+ }
+
+ return groupMembers( ( *it ).groupID, it );
+ }
+ }
+ return IngredientList();
+}
diff --git a/krecipes/src/datablocks/ingredientlist.h b/krecipes/src/datablocks/ingredientlist.h
new file mode 100644
index 0000000..9f01831
--- /dev/null
+++ b/krecipes/src/datablocks/ingredientlist.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef INGREDIENTLIST_H
+#define INGREDIENTLIST_H
+
+#include <qvaluelist.h>
+#include <qregexp.h>
+
+#include "datablocks/ingredient.h"
+
+class RecipeDB;
+
+/**
+@author Unai Garro
+*/
+class IngredientList: public QValueList <Ingredient>
+{
+public:
+ IngredientList();
+ ~IngredientList();
+ bool contains( const Ingredient &ing, bool compareAmount = false, RecipeDB *database = 0 ) const;
+ bool containsSubSet( IngredientList &il, IngredientList &missing, bool compareAmount = false, RecipeDB *database = 0 );
+ bool containsSubSet( IngredientList &il ) const;
+ bool containsAny( const IngredientList &, bool compareAmount = false, RecipeDB *database = 0 );
+
+ IngredientList groupMembers( int id, IngredientList::const_iterator begin ) const;
+
+ void move( int index1, int index2 );
+ void move( int index1, int count, int index2 );
+ void empty( void );
+ int find( int id ) const;
+ Ingredient findByName( const QString & ) const;
+ Ingredient findByName( const QRegExp & ) const;
+ IngredientList::const_iterator find( IngredientList::const_iterator, int id ) const;
+ IngredientList::iterator find( IngredientList::iterator, int id );
+
+ /** Warning, returns an invalid reference if no ingredient is found. Must check prior
+ * to calling this function if the ingredient exists.
+ */
+ IngredientData& findSubstitute( const Ingredient & );
+ void removeSubstitute( const Ingredient & );
+
+ IngredientList firstGroup();
+ IngredientList nextGroup();
+
+private:
+ QValueList<IngredientList::const_iterator> _groupMembers( int id, IngredientList::const_iterator begin ) const;
+ QValueList<IngredientList::const_iterator> usedGroups;
+};
+
+#endif
diff --git a/krecipes/src/datablocks/ingredientproperty.cpp b/krecipes/src/datablocks/ingredientproperty.cpp
new file mode 100644
index 0000000..331ee93
--- /dev/null
+++ b/krecipes/src/datablocks/ingredientproperty.cpp
@@ -0,0 +1,32 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "datablocks/ingredientproperty.h"
+
+IngredientProperty::IngredientProperty()
+{
+ id = -1;
+ ingredientID = -1;
+ amount = 0.0;
+}
+
+IngredientProperty::IngredientProperty( const QString &_name, const QString &_units, int _id ) :
+ id( _id ),
+ name( _name ),
+ units( _units )
+{}
+
+IngredientProperty::~IngredientProperty()
+{}
+
+bool IngredientProperty::operator==( const IngredientProperty &prop ) const
+{
+ return ( prop.id == id );
+}
+
diff --git a/krecipes/src/datablocks/ingredientproperty.h b/krecipes/src/datablocks/ingredientproperty.h
new file mode 100644
index 0000000..0acceaf
--- /dev/null
+++ b/krecipes/src/datablocks/ingredientproperty.h
@@ -0,0 +1,37 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef INGREDIENTPROPERTY_H
+#define INGREDIENTPROPERTY_H
+
+#include <qstring.h>
+
+#include "unit.h"
+
+/**
+@author Unai Garro
+*/
+class IngredientProperty
+{
+public:
+ IngredientProperty();
+ IngredientProperty( const QString &name, const QString &units, int id = -1 );
+ ~IngredientProperty();
+ int id; // The property's id
+ int ingredientID; // (Optional) reference to the ingredient to which is attached
+ QString name; // Name of the property
+ QString units; // The units that the property uses
+ Unit perUnit; // stores the unit ID, name, and type of the per units.
+ double amount; // Stores the amount, in the case of being attached to an ingredient. If not attached to any, you can set it to -1 preferably. That's the case in which the property is treated as a characteristic any without value (amount).
+
+ /** Compare two elements by their id */
+ bool operator==( const IngredientProperty & ) const;
+};
+
+#endif
diff --git a/krecipes/src/datablocks/ingredientpropertylist.cpp b/krecipes/src/datablocks/ingredientpropertylist.cpp
new file mode 100644
index 0000000..59dc646
--- /dev/null
+++ b/krecipes/src/datablocks/ingredientpropertylist.cpp
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "datablocks/ingredientpropertylist.h"
+
+IngredientPropertyList::IngredientPropertyList()
+{}
+
+
+IngredientPropertyList::~IngredientPropertyList()
+{}
+
+IngredientPropertyList::const_iterator IngredientPropertyList::find( int id )
+{
+ IngredientProperty ip;
+ ip.id = id;
+ return QValueList<IngredientProperty>::find( ip );
+}
+
+int IngredientPropertyList::findByName( const QString &name )
+{
+ IngredientPropertyList::const_iterator prop_it;
+ for ( prop_it = begin(); prop_it != end(); ++prop_it ) {
+ if ( (*prop_it).name == name )
+ return (*prop_it).id;
+ }
+
+ return -1;
+}
+
+void IngredientPropertyList::divide( double units_of_yield_type )
+{
+ IngredientPropertyList::iterator prop_it;
+ for ( prop_it = begin(); prop_it != end(); ++prop_it )
+ (*prop_it).amount /= units_of_yield_type;
+}
+
+void IngredientPropertyList::filter( int ingredientID, IngredientPropertyList *filteredList )
+{
+ filteredList->clear();
+ IngredientPropertyList::const_iterator prop_it;
+ for ( prop_it = begin(); prop_it != end(); ++prop_it ) {
+ if ( (*prop_it).ingredientID == ingredientID )
+ filteredList->append( *prop_it );
+ }
+}
diff --git a/krecipes/src/datablocks/ingredientpropertylist.h b/krecipes/src/datablocks/ingredientpropertylist.h
new file mode 100644
index 0000000..88cd1c2
--- /dev/null
+++ b/krecipes/src/datablocks/ingredientpropertylist.h
@@ -0,0 +1,30 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef INGREDIENTPROPERTYLIST_H
+#define INGREDIENTPROPERTYLIST_H
+
+#include <qvaluelist.h>
+
+#include "datablocks/ingredientproperty.h"
+
+class IngredientPropertyList : public QValueList<IngredientProperty>
+{
+public:
+ IngredientPropertyList();
+
+ ~IngredientPropertyList();
+
+ void divide( double units_of_yield_type );
+ IngredientPropertyList::const_iterator find( int id );
+ int findByName( const QString & );
+ void filter( int ingredientID, IngredientPropertyList *filteredList );
+};
+
+#endif
diff --git a/krecipes/src/datablocks/kreborder.cpp b/krecipes/src/datablocks/kreborder.cpp
new file mode 100644
index 0000000..11ff382
--- /dev/null
+++ b/krecipes/src/datablocks/kreborder.cpp
@@ -0,0 +1,17 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "kreborder.h"
+
+//typedef enum KreBorderStyle { None = 0, Dotted, Dashed, Solid, Double, Groove, Ridge, Inset, Outset };
+
+KreBorder::KreBorder( int w, const QString & s, const QColor &c ) : width( w ), style( s ), color( c )
+{
+}
diff --git a/krecipes/src/datablocks/kreborder.h b/krecipes/src/datablocks/kreborder.h
new file mode 100644
index 0000000..caf38dd
--- /dev/null
+++ b/krecipes/src/datablocks/kreborder.h
@@ -0,0 +1,29 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KREBORDER_H
+#define KREBORDER_H
+
+#include <qcolor.h>
+#include <qstring.h>
+
+//typedef enum KreBorderStyle { None = 0, Dotted, Dashed, Solid, Double, Groove, Ridge, Inset, Outset };
+
+class KreBorder
+{
+public:
+ KreBorder( int w = 1, const QString & s = "none", const QColor &c = Qt::black );
+
+ int width;
+ QString style;
+ QColor color;
+};
+
+#endif //KREBORDER_H
diff --git a/krecipes/src/datablocks/mixednumber.cpp b/krecipes/src/datablocks/mixednumber.cpp
new file mode 100644
index 0000000..bc999f2
--- /dev/null
+++ b/krecipes/src/datablocks/mixednumber.cpp
@@ -0,0 +1,289 @@
+/***************************************************************************
+* Copyright (C) 2003 by krecipes.sourceforge.net authors *
+* *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "mixednumber.h"
+
+#include <qregexp.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+QString beautify( const QString &num )
+{
+ QString copy( num );
+ copy.remove( QRegExp( QString( "(%1){0,1}0+$" ).arg( QRegExp::escape( KGlobal::locale() ->decimalSymbol() ) ) ) );
+ return copy;
+}
+
+MixedNumber::MixedNumber() :
+ m_whole( 0 ),
+ m_numerator( 0 ),
+ m_denominator( 1 ),
+ locale( KGlobal::locale() )
+{}
+
+MixedNumber::MixedNumber( int whole, int numerator, int denominator ) :
+ m_whole( whole ),
+ m_numerator( numerator ),
+ m_denominator( denominator ),
+ locale( KGlobal::locale() )
+{}
+
+MixedNumber::MixedNumber( double decimal, double precision ) :
+ locale( KGlobal::locale() )
+{
+ // find nearest fraction
+ int intPart = static_cast<int>( decimal );
+ decimal -= static_cast<double>( intPart );
+
+ MixedNumber low( 0, 0, 1 ); // "A" = 0/1
+ MixedNumber high( 0, 1, 1 ); // "B" = 1/1
+
+ for ( int i = 0; i < 100; ++i ) {
+ double testLow = low.denominator() * decimal - low.numerator();
+ double testHigh = high.numerator() - high.denominator() * decimal;
+
+ if ( testHigh < precision * high.denominator() )
+ break; // high is answer
+ if ( testLow < precision * low.denominator() ) { // low is answer
+ high = low;
+ break;
+ }
+
+ if ( i & 1 ) { // odd step: add multiple of low to high
+ double test = testHigh / testLow;
+ int count = ( int ) test; // "N"
+ int num = ( count + 1 ) * low.numerator() + high.numerator();
+ int denom = ( count + 1 ) * low.denominator() + high.denominator();
+
+ if ( ( num > 0x8000 ) || ( denom > 0x10000 ) )
+ break;
+
+ high.setNumerator( num - low.numerator() ); // new "A"
+ high.setDenominator( denom - low.denominator() );
+ low.setNumerator( num ); // new "B"
+ low.setDenominator( denom );
+ }
+ else { // even step: add multiple of high to low
+ double test = testLow / testHigh;
+ int count = ( int ) test; // "N"
+ int num = low.numerator() + ( count + 1 ) * high.numerator();
+ int denom = low.denominator() + ( count + 1 ) * high.denominator();
+
+ if ( ( num > 0x10000 ) || ( denom > 0x10000 ) )
+ break;
+
+ low.setNumerator( num - high.numerator() ); // new "A"
+ low.setDenominator( denom - high.denominator() );
+ high.setNumerator( num ); // new "B"
+ high.setDenominator( denom );
+ }
+ }
+
+ m_numerator = high.numerator();
+ m_denominator = high.denominator();
+ m_whole = intPart;
+}
+
+MixedNumber::~MixedNumber()
+{}
+
+int MixedNumber::getNumerator( const QString &input, int space_index, int slash_index, bool *ok )
+{
+ return input.mid( space_index + 1, slash_index - space_index - 1 ).toInt( ok );
+}
+
+int MixedNumber::getDenominator( const QString &input, int slash_index, bool *ok )
+{
+ return input.mid( slash_index + 1, input.length() ).toInt( ok );
+}
+
+MixedNumber MixedNumber::fromString( const QString &str, bool *ok, bool locale_aware )
+{
+ QString input = str.stripWhiteSpace();
+ if ( input.isEmpty() ) {
+ if ( ok ) {
+ *ok = true;
+ }
+ return MixedNumber();
+ }
+
+ KLocale *locale = KGlobal::locale();
+
+ bool num_ok;
+
+ int whole;
+ int numerator;
+ int denominator;
+
+ int space_index = input.find( " " );
+ int slash_index = input.find( "/" );
+
+ if ( space_index == -1 ) {
+ if ( slash_index == -1 ) //input contains no fractional part
+ {
+ QString decimal_symbol = ( locale_aware ) ? locale->decimalSymbol() : ".";
+ if ( input.endsWith( decimal_symbol ) )
+ {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ double decimal = ( locale_aware ) ? locale->readNumber( input, &num_ok ) : input.toDouble( &num_ok );
+
+ if ( !num_ok )
+ {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ if ( ok )
+ {
+ *ok = true;
+ }
+ return MixedNumber( decimal );
+ }
+ else //input just contains a fraction
+ {
+ whole = 0;
+
+ numerator = MixedNumber::getNumerator( input, space_index, slash_index, &num_ok );
+ if ( !num_ok ) {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ denominator = MixedNumber::getDenominator( input, slash_index, &num_ok );
+ if ( !num_ok || denominator == 0 ) {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ if ( ok ) {
+ *ok = true;
+ }
+ return MixedNumber( whole, numerator, denominator );
+ }
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ whole = input.mid( 0, space_index ).toInt( &num_ok );
+ if ( !num_ok ) {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ numerator = MixedNumber::getNumerator( input, space_index, slash_index, &num_ok );
+ if ( !num_ok ) {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ denominator = MixedNumber::getDenominator( input, slash_index, &num_ok );
+ if ( !num_ok || denominator == 0 ) {
+ if ( ok ) {
+ *ok = false;
+ }
+ return MixedNumber();
+ }
+
+ if ( ok ) {
+ *ok = true;
+ }
+ return MixedNumber( whole, numerator, denominator );
+}
+
+QString MixedNumber::toString( Format format, bool locale_aware ) const
+{
+ if ( format == DecimalFormat ) {
+ if ( locale_aware )
+ return beautify( locale->formatNumber( toDouble(), 5 ) );
+ else
+ return QString::number( toDouble() );
+ }
+
+ if ( m_numerator == 0 && m_whole == 0 )
+ return QString( "0" );
+
+
+ QString result;
+
+ if ( m_whole != 0 ) {
+ result += QString::number( m_whole );
+ if ( m_numerator != 0 )
+ result += " ";
+ }
+
+ if ( m_numerator != 0 )
+ result += QString::number( m_numerator ) + "/" + QString::number( m_denominator );
+
+ return result;
+}
+
+bool MixedNumber::operator!=( const MixedNumber &fraction )
+{
+ return ( fraction.toDouble() != toDouble() );
+}
+
+MixedNumber& MixedNumber::operator+=( const MixedNumber &fraction )
+{
+ m_numerator = ( m_numerator * fraction.m_denominator ) + ( m_denominator * fraction.m_numerator );
+ m_denominator = m_denominator * fraction.m_denominator;
+ m_whole += fraction.m_whole;
+ simplify();
+
+ return *this;
+}
+
+MixedNumber& MixedNumber::operator+=( double d )
+{
+ MixedNumber mn(d);
+ *this += mn;
+ return *this;
+}
+
+void MixedNumber::simplify()
+{
+ int divisor = gcd( m_numerator, m_denominator );
+ m_numerator /= divisor;
+ m_denominator /= divisor;
+}
+
+double MixedNumber::toDouble() const
+{
+ return static_cast<double>( m_whole ) + ( static_cast<double>( m_numerator ) / static_cast<double>( m_denominator ) );
+}
+
+int MixedNumber::gcd( int n, int m )
+{
+ int r;
+ while ( n != 0 ) {
+ r = m % n;
+ m = n;
+ n = r;
+ }
+
+ return m;
+}
diff --git a/krecipes/src/datablocks/mixednumber.h b/krecipes/src/datablocks/mixednumber.h
new file mode 100644
index 0000000..2c39c50
--- /dev/null
+++ b/krecipes/src/datablocks/mixednumber.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef MIXEDNUMBER_H
+#define MIXEDNUMBER_H
+
+#include <qstring.h>
+
+/** remove any extra zeros on the end of the string and the decimal if a whole number */
+QString beautify( const QString &num );
+
+class KLocale;
+
+/** A class to hold and manipulate a mixed number.
+ * @author Jason Kivlighn
+ */
+class MixedNumber
+{
+public:
+ MixedNumber( int whole, int numerator, int denominator );
+
+ /** Create a mixed number from the given @param decimal. This uses a method that will
+ * approximate the actual fraction with precision equal to @param precision.
+ * The closer @param precision is to zero without being zero, the more precisely
+ * it will try to approximate the fraction.
+ */
+ MixedNumber( double decimal, double precision = 1e-6 );
+
+ /** Creates a mixed number with an initial value of zero. */
+ MixedNumber();
+
+ ~MixedNumber();
+
+ MixedNumber& operator+=( const MixedNumber & );
+ MixedNumber& operator+=( double );
+ bool operator!=( const MixedNumber &fraction );
+ bool operator>( double d )
+ {
+ return ( toDouble() > d );
+ }
+
+ typedef enum Format { DecimalFormat, MixedNumberFormat };
+
+ /** The input as a decimal. */
+ double toDouble() const;
+
+ /** Returns the fraction as a string */
+ QString toString( Format = MixedNumberFormat, bool locale_aware = true ) const;
+
+ /** The whole part of the input */
+ int whole() const
+ {
+ return m_whole;
+ }
+
+ /** The numerator of the fractional part of the input. */
+ int numerator() const
+ {
+ return m_numerator;
+ }
+
+ /** The denominator of the fractional part of the input. */
+ int denominator() const
+ {
+ return m_denominator;
+ }
+
+ void setNumerator( int n )
+ {
+ m_numerator = n;
+ }
+ void setDenominator( int d )
+ {
+ m_denominator = d;
+ }
+
+ /** Ensure that the fraction is simplified to its lowest terms. */
+ void simplify();
+
+ /** Parses the given QString as a mixed number. The input can be
+ * expressed as a mixed number in the form "a b/c", or as a decimal.
+ */
+ static MixedNumber fromString( const QString &input, bool *ok = 0, bool locale_aware = true );
+
+private:
+ static int getNumerator( const QString &input, int space_index, int slash_index, bool *ok );
+ static int getDenominator( const QString &input, int slash_index, bool *ok );
+
+ int gcd( int, int );
+
+ int m_whole;
+ int m_numerator;
+ int m_denominator;
+
+ KLocale *locale;
+};
+
+inline const MixedNumber operator+( const MixedNumber &mn1, const MixedNumber &mn2 )
+{
+ MixedNumber tmp = mn1;
+ tmp += mn2;
+ return tmp;
+}
+
+
+inline const MixedNumber operator+( double d, const MixedNumber &mn )
+{
+ MixedNumber tmp = mn;
+ tmp += d;
+ return tmp;
+}
+
+inline const MixedNumber operator+( const MixedNumber &mn, double d )
+{
+ return operator+(d,mn);
+}
+
+
+#endif //MIXEDNUMBER_H
diff --git a/krecipes/src/datablocks/rating.cpp b/krecipes/src/datablocks/rating.cpp
new file mode 100644
index 0000000..e2c0a3f
--- /dev/null
+++ b/krecipes/src/datablocks/rating.cpp
@@ -0,0 +1,86 @@
+/***************************************************************************
+* Copyright (C) 2005 by Jason Kivlighn *
+* jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "rating.h"
+
+#include <qpainter.h>
+#include <qbitmap.h>
+
+#include <kiconloader.h>
+
+QPixmap Rating::starsPixmap( double stars_d, bool include_empty )
+{
+ int stars = qRound(stars_d * 2); //multiply by two to make it easier to work with half-stars
+
+ QPixmap star = UserIcon(QString::fromLatin1("star_on"));
+ QPixmap star_off;
+ if ( include_empty )
+ star_off = UserIcon(QString::fromLatin1("star_off"));
+
+ int pixmapWidth;
+ if ( include_empty )
+ pixmapWidth = 18*5;
+ else
+ pixmapWidth = 18*(stars/2)+((stars%2==1)?9:0);
+
+ QPixmap generatedPixmap(pixmapWidth,18);
+
+ if ( !generatedPixmap.isNull() ) { //there aren't zero stars
+ generatedPixmap.fill();
+ QPainter painter( &generatedPixmap );
+
+ int pixmapWidth = 18*(stars/2)+((stars%2==1)?9:0);
+ if ( include_empty )
+ painter.drawTiledPixmap(0,0,18*5,18,star_off); //fill with empty stars
+ painter.drawTiledPixmap(0,0,pixmapWidth,18,star); //write over the empty stars to show the rating
+ }
+
+ generatedPixmap.setMask( generatedPixmap.createHeuristicMask() );
+
+ return generatedPixmap;
+}
+
+void Rating::append( const RatingCriteria &rc )
+{
+ ratingCriteriaList.append( rc );
+}
+
+double Rating::average() const
+{
+ double sum = 0;
+ int count = 0;
+ for ( RatingCriteriaList::const_iterator rc_it = ratingCriteriaList.begin(); rc_it != ratingCriteriaList.end(); ++rc_it ) {
+ count++;
+ sum += (*rc_it).stars;
+ }
+
+ if ( count > 0 )
+ return sum/count;
+ else
+ return -1;
+}
+
+
+double RatingList::average()
+{
+ int rating_total = 0;
+ double rating_sum = 0;
+ for ( RatingList::const_iterator rating_it = begin(); rating_it != end(); ++rating_it ) {
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ rating_total++;
+ rating_sum += (*rc_it).stars;
+ }
+ }
+
+ if ( rating_total > 0 )
+ return rating_sum/rating_total;
+ else
+ return -1;
+}
diff --git a/krecipes/src/datablocks/rating.h b/krecipes/src/datablocks/rating.h
new file mode 100644
index 0000000..3f65340
--- /dev/null
+++ b/krecipes/src/datablocks/rating.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+* Copyright (C) 2005 by Jason Kivlighn *
+* jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RATING_H
+#define RATING_H
+
+#include <qvaluelist.h>
+#include <qstring.h>
+
+#include <qpixmap.h>
+
+class RatingCriteria
+{
+public:
+ RatingCriteria() : id(-1), stars(0.0){}
+
+ int id;
+ QString name;
+ double stars;
+};
+
+typedef QValueList< RatingCriteria > RatingCriteriaList;
+
+class Rating
+{
+public:
+ Rating() : id(-1){}
+
+ static QPixmap starsPixmap( double stars_d, bool include_empty = false );
+
+ void append( const RatingCriteria & );
+
+ double average() const;
+
+ int id;
+ QString comment;
+ QString rater;
+
+ RatingCriteriaList ratingCriteriaList;
+};
+
+class RatingList : public QValueList< Rating >
+{
+public:
+ double average();
+};
+
+#endif
diff --git a/krecipes/src/datablocks/recipe.cpp b/krecipes/src/datablocks/recipe.cpp
new file mode 100644
index 0000000..c1ef971
--- /dev/null
+++ b/krecipes/src/datablocks/recipe.cpp
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "datablocks/recipe.h"
+
+Recipe::Recipe()
+{
+ empty(); //Create & initialize the recipe empty originally
+}
+
+Recipe::~Recipe()
+{}
+
+void Recipe::empty( void )
+{
+ recipeID = -1;
+
+ yield.amount = 1;
+ yield.amount_offset = 0;
+ yield.type = QString::null;
+
+ title = QString::null;
+ instructions = QString::null;
+ photo = QPixmap();
+ ingList.empty();
+ categoryList.clear();
+ authorList.clear();
+ ratingList.clear();
+ prepTime = QTime( 0, 0 );
+}
+
+
+QString Yield::amountToString() const
+{
+ QString ret = QString::number(amount);
+ if ( amount_offset > 0 )
+ ret += "-"+QString::number(amount+amount_offset);
+
+ return ret;
+}
+
+QString Yield::toString() const
+{
+ return amountToString() + " " + type;
+}
+
diff --git a/krecipes/src/datablocks/recipe.h b/krecipes/src/datablocks/recipe.h
new file mode 100644
index 0000000..536864c
--- /dev/null
+++ b/krecipes/src/datablocks/recipe.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef RECIPE_H
+#define RECIPE_H
+
+#include <qstring.h>
+#include <qpixmap.h>
+#include <qdatetime.h>
+
+#include "ingredientlist.h"
+#include "datablocks/rating.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+
+class Yield
+{
+public:
+ Yield() : amount(1), amount_offset(0), type(QString::null), type_id(-1){}
+
+ QString amountToString() const;
+ QString toString() const;
+
+ double amount;
+ double amount_offset;
+ QString type;
+ int type_id;
+};
+
+/**
+@author Unai Garro
+*/
+class Recipe
+{
+public:
+ Recipe();
+ ~Recipe();
+ // Public variables
+
+ int recipeID;
+ Yield yield;
+ QString title;
+ QString instructions;
+ QPixmap photo;
+ IngredientList ingList;
+ ElementList categoryList; // id+name
+ ElementList authorList; //authors' id+name
+ QTime prepTime;
+
+ QDateTime ctime;
+ QDateTime mtime;
+ QDateTime atime;
+
+ RatingList ratingList;
+ IngredientPropertyList properties;
+
+ // Public methods
+ void empty( void );
+
+
+
+};
+
+#endif
diff --git a/krecipes/src/datablocks/recipelist.cpp b/krecipes/src/datablocks/recipelist.cpp
new file mode 100644
index 0000000..524df68
--- /dev/null
+++ b/krecipes/src/datablocks/recipelist.cpp
@@ -0,0 +1,18 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipelist.h"
+
+RecipeList::RecipeList() : QValueList <Recipe>()
+{}
+
+RecipeList::~RecipeList()
+{}
+
diff --git a/krecipes/src/datablocks/recipelist.h b/krecipes/src/datablocks/recipelist.h
new file mode 100644
index 0000000..e3c90aa
--- /dev/null
+++ b/krecipes/src/datablocks/recipelist.h
@@ -0,0 +1,28 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro ugarro@users.sourceforge.net *
+* *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPELIST_H
+#define RECIPELIST_H
+
+#include "datablocks/recipe.h"
+
+#include <qvaluelist.h>
+
+/**
+@author Unai Garro
+*/
+class RecipeList: public QValueList <Recipe>
+{
+public:
+ RecipeList();
+ ~RecipeList();
+};
+
+#endif
diff --git a/krecipes/src/datablocks/unit.cpp b/krecipes/src/datablocks/unit.cpp
new file mode 100644
index 0000000..fbd94de
--- /dev/null
+++ b/krecipes/src/datablocks/unit.cpp
@@ -0,0 +1,75 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "unit.h"
+
+Unit::Unit() : type(Unit::Other), id( -1 )
+{}
+
+Unit::Unit( const QString &_name, const QString &_plural, int _id ) :
+ type(Unit::Other),
+ id( _id ),
+ name( _name ),
+ plural( _plural )
+{}
+
+Unit::Unit( const QString &_name, double amount ) : type(Unit::Other)
+{
+ if ( amount > 1 )
+ plural = _name;
+ else
+ name = _name;
+}
+
+QString Unit::determineName( double amount, bool useAbbrev ) const
+{
+ if ( useAbbrev ) {
+ QString unit = ( amount > 1 ) ? plural_abbrev : name_abbrev;
+ if ( unit.isEmpty() )
+ unit = ( amount > 1 ) ? plural : name;
+ return unit;
+ }
+ else
+ return ( amount > 1 ) ? plural : name;
+}
+
+bool Unit::operator==( const Unit &u ) const
+{
+ //treat QString::null and "" as the same
+ QString plural_test1 = u.plural.lower();
+ if ( plural_test1.isNull() )
+ plural_test1 = "";
+
+ QString plural_test2 = plural.lower();
+ if ( plural_test2.isNull() )
+ plural_test2 = "";
+
+ QString single_test1 = u.name.lower();
+ if ( single_test1.isNull() )
+ single_test1 = "";
+
+ QString single_test2 = name.lower();
+ if ( single_test2.isNull() )
+ single_test2 = "";
+
+ if ( plural_test1.isEmpty() && plural_test2.isEmpty() && single_test1.isEmpty() && single_test2.isEmpty() )
+ return true;
+ else if ( plural_test1.isEmpty() && plural_test2.isEmpty() )
+ return single_test1 == single_test2;
+ else if ( single_test1.isEmpty() && single_test2.isEmpty() )
+ return plural_test1 == plural_test2;
+ else
+ return ( plural_test1 == plural_test2 || single_test1 == single_test2 );
+}
+
+bool Unit::operator<( const Unit &u ) const
+{
+ return ( QString::localeAwareCompare( name.lower(), u.name.lower() ) < 0 );
+}
diff --git a/krecipes/src/datablocks/unit.h b/krecipes/src/datablocks/unit.h
new file mode 100644
index 0000000..73ae794
--- /dev/null
+++ b/krecipes/src/datablocks/unit.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* Copyright (C) 2004-2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef UNIT_H
+#define UNIT_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+class Unit
+{
+public:
+ Unit();
+ Unit( const QString &name, const QString &plural, int id = -1 );
+
+ typedef enum { All = -1, Other = 0, Mass, Volume } Type;
+
+ /** Use @param amount to determine whether to use @param name as the plural or singlular form */
+ Unit( const QString &name, double amount );
+
+ bool operator==( const Unit &u ) const;
+ bool operator<( const Unit &u ) const;
+
+ QString determineName( double amount, bool useAbbrev ) const;
+
+ Type type;
+
+ int id;
+ QString name;
+ QString plural;
+
+ QString name_abbrev;
+ QString plural_abbrev;
+};
+
+typedef QValueList< Unit > UnitList;
+
+#endif //UNIT_H
diff --git a/krecipes/src/datablocks/unitratio.cpp b/krecipes/src/datablocks/unitratio.cpp
new file mode 100644
index 0000000..ce8e5ad
--- /dev/null
+++ b/krecipes/src/datablocks/unitratio.cpp
@@ -0,0 +1,30 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "unitratio.h"
+#include "klocale.h"
+
+UnitRatio::UnitRatio()
+{
+ uID1 = -1;
+ uID2 = -1;
+ ratio = -1;
+}
+
+UnitRatio::UnitRatio( const UnitRatio &ur )
+{
+ uID1 = ur.uID1;
+ uID2 = ur.uID2;
+ ratio = ur.ratio;
+}
+
+UnitRatio::~UnitRatio()
+{}
+
+
diff --git a/krecipes/src/datablocks/unitratio.h b/krecipes/src/datablocks/unitratio.h
new file mode 100644
index 0000000..dd327f1
--- /dev/null
+++ b/krecipes/src/datablocks/unitratio.h
@@ -0,0 +1,37 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef UNITRATIO_H
+#define UNITRATIO_H
+
+/**
+@author Unai Garro
+*/
+class UnitRatio
+{
+public:
+
+ UnitRatio();
+ UnitRatio( const UnitRatio &ur );
+ ~UnitRatio();
+
+ bool operator!=( const UnitRatio &r ) const
+ {
+ return !( r.uID1 == uID1 && r.uID2 == uID2 );
+ }
+ bool operator==( const UnitRatio &r ) const
+ {
+ return ( r.uID1 == uID1 && r.uID2 == uID2 );
+ }
+
+ int uID1, uID2;
+ double ratio;
+};
+
+#endif
diff --git a/krecipes/src/datablocks/unitratiolist.cpp b/krecipes/src/datablocks/unitratiolist.cpp
new file mode 100644
index 0000000..51a3203
--- /dev/null
+++ b/krecipes/src/datablocks/unitratiolist.cpp
@@ -0,0 +1,34 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "unitratiolist.h"
+#include "klocale.h"
+
+
+UnitRatioList::UnitRatioList()
+{}
+
+
+UnitRatioList::~UnitRatioList()
+{}
+
+double UnitRatioList::getRatio( int uid1, int uid2 )
+{
+ if ( uid1 == uid2 )
+ return ( 1.0 );
+ else {
+ for ( UnitRatioList::const_iterator ur_it = begin();ur_it != end(); ++ur_it ) {
+ if ( ( *ur_it ).uID1 == uid1 && ( *ur_it ).uID2 == uid2 )
+ return ( ( *ur_it ).ratio );
+ else if ( ( *ur_it ).uID1 == uid2 && ( *ur_it ).uID2 == uid1 )
+ return ( 1.0 / ( *ur_it ).ratio );
+ }
+ return ( -1.0 );
+ }
+}
diff --git a/krecipes/src/datablocks/unitratiolist.h b/krecipes/src/datablocks/unitratiolist.h
new file mode 100644
index 0000000..22a49a8
--- /dev/null
+++ b/krecipes/src/datablocks/unitratiolist.h
@@ -0,0 +1,33 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef UNITRATIOLIST_H
+#define UNITRATIOLIST_H
+#include <qvaluelist.h>
+#include "unitratio.h"
+
+
+/**
+@author Unai Garro
+*/
+class UnitRatioList : public QValueList <UnitRatio>
+{
+public:
+ UnitRatioList();
+ ~UnitRatioList();
+
+ void add
+ ( const UnitRatio &r )
+ {
+ append( r );
+ }
+ double getRatio( int uid1, int uid2 );
+};
+
+#endif
diff --git a/krecipes/src/datablocks/weight.cpp b/krecipes/src/datablocks/weight.cpp
new file mode 100644
index 0000000..08ba695
--- /dev/null
+++ b/krecipes/src/datablocks/weight.cpp
@@ -0,0 +1,14 @@
+/***************************************************************************
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "weight.h"
+
+Weight::Weight() : id(-1), prepMethodID(-1)
+{}
+
diff --git a/krecipes/src/datablocks/weight.h b/krecipes/src/datablocks/weight.h
new file mode 100644
index 0000000..eefe896
--- /dev/null
+++ b/krecipes/src/datablocks/weight.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef WEIGHT_H
+#define WEIGHT_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#include "datablocks/elementlist.h"
+
+class Weight
+{
+public:
+ Weight();
+
+ int id;
+ int ingredientID;
+ int perAmountUnitID;
+ QString perAmountUnit;
+ double perAmount;
+ int weightUnitID;
+ double weight;
+ QString weightUnit;
+ int prepMethodID;
+ QString prepMethod;
+};
+
+class WeightList : public QValueList<Weight>
+{
+public:
+ WeightList() : QValueList<Weight>(){}
+};
+
+#endif
diff --git a/krecipes/src/dialogs/Makefile.am b/krecipes/src/dialogs/Makefile.am
new file mode 100644
index 0000000..d62566d
--- /dev/null
+++ b/krecipes/src/dialogs/Makefile.am
@@ -0,0 +1,38 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir)/.. $(all_includes)
+
+KDE_CXXFLAGS = $(USE_EXCEPTIONS)
+
+noinst_LTLIBRARIES=libkrecipesdialogs.la
+libkrecipesdialogs_la_SOURCES= \
+ advancedsearchdialog.cpp recipeimportdialog.cpp \
+ dietwizarddialog.cpp recipeinputdialog.cpp \
+ recipeviewdialog.cpp selectrecipedialog.cpp \
+ ingredientsdialog.cpp selectunitdialog.cpp \
+ createelementdialog.cpp propertiesdialog.cpp \
+ createpropertydialog.cpp selectpropertydialog.cpp \
+ unitsdialog.cpp dependanciesdialog.cpp \
+ shoppinglistdialog.cpp shoppinglistviewdialog.cpp \
+ selectcategoriesdialog.cpp categorieseditordialog.cpp \
+ authorsdialog.cpp selectauthorsdialog.cpp \
+ resizerecipedialog.cpp \
+ dietviewdialog.cpp ingredientmatcherdialog.cpp \
+ usdadatadialog.cpp prepmethodsdialog.cpp \
+ createcategorydialog.cpp borderdialog.cpp \
+ refineshoppinglistdialog.cpp pagesetupdialog.cpp \
+ dbimportdialog.cpp createunitdialog.cpp \
+ setupdisplay.cpp \
+ ingredientparserdialog.cpp ingredientgroupsdialog.cpp \
+ editratingdialog.cpp similarcategoriesdialog.cpp \
+ conversiondialog.cpp createingredientweightdialog.cpp \
+ recipeprintpreview.cpp
+
+libkrecipesdialogs_la_METASOURCES=AUTO
+
+#the library search path.
+libkrecipesdialogs_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/dialogs/advancedsearchdialog.cpp b/krecipes/src/dialogs/advancedsearchdialog.cpp
new file mode 100644
index 0000000..30d50a5
--- /dev/null
+++ b/krecipes/src/dialogs/advancedsearchdialog.cpp
@@ -0,0 +1,933 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "advancedsearchdialog.h"
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <qtabwidget.h>
+#include <qwidget.h>
+#include <qcheckbox.h>
+#include <qframe.h>
+#include <qcombobox.h>
+#include <qheader.h>
+#include <qlistview.h>
+#include <qgroupbox.h>
+#include <qspinbox.h>
+#include <qdatetimeedit.h>
+#include <kpushbutton.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qscrollview.h>
+#include <qhbox.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kpopupmenu.h>
+
+#include "backends/recipedb.h"
+#include "backends/searchparameters.h"
+#include "recipeactionshandler.h"
+#include "widgets/recipelistview.h"
+#include "widgets/kdateedit.h"
+#include "widgets/ratingwidget.h"
+#include "widgets/fractioninput.h"
+#include "widgets/criteriacombobox.h"
+
+#include "profiling.h"
+
+AdvancedSearchDialog::AdvancedSearchDialog( QWidget *parent, RecipeDB *db ) : QWidget( parent ),
+ database( db )
+{
+ ///
+ ///BEGIN OF AUTOMATICALLY GENERATED GUI CODE///
+ ///
+ AdvancedSearchDialogLayout = new QHBoxLayout( this, 5, 3, "AdvancedSearchDialogLayout");
+
+ layout7 = new QVBoxLayout( 0, 0, 3, "layout7");
+
+ textLabel1_4 = new QLabel( this, "textLabel1_4" );
+ layout7->addWidget( textLabel1_4 );
+
+ scrollView1 = new QScrollView( this, "scrollView1" );
+ scrollView1->enableClipper(true);
+
+ parametersFrame = new QFrame( scrollView1, "parametersFrame" );
+ parametersFrame->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)3, (QSizePolicy::SizeType)3, 0, 0, parametersFrame->sizePolicy().hasHeightForWidth() ) );
+ parametersFrame->setFrameShape( QFrame::NoFrame );
+ parametersFrame->setFrameShadow( QFrame::Plain );
+ parametersFrame->setLineWidth( 0 );
+ parametersFrameLayout = new QVBoxLayout( parametersFrame, 0, 0, "parametersFrameLayout");
+
+ titleButton = new QPushButton( parametersFrame, "titleButton" );
+ titleButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( titleButton );
+
+ titleFrame = new QFrame( parametersFrame, "titleFrame" );
+ titleFrame->setFrameShape( QFrame::StyledPanel );
+ titleFrame->setFrameShadow( QFrame::Raised );
+ titleFrameLayout = new QVBoxLayout( titleFrame, 5, 3, "titleFrameLayout");
+
+ requireAllTitle = new QCheckBox( i18n("Require All Words"), titleFrame );
+ titleFrameLayout->addWidget( requireAllTitle );
+
+ QHBox *titleHBox = new QHBox( titleFrame );
+ /*QLabel *titleInfoLabel = */new QLabel(i18n("Keywords:"),titleHBox);
+ titleEdit = new QLineEdit( titleHBox, "titleEdit" );
+ titleFrameLayout->addWidget( titleHBox );
+
+ parametersFrameLayout->addWidget( titleFrame );
+ titleFrameSpacer = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( titleFrameSpacer );
+
+
+ ingredientButton = new QPushButton( parametersFrame, "ingredientButton" );
+ ingredientButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( ingredientButton );
+
+ ingredientFrame = new QFrame( parametersFrame, "ingredientFrame" );
+ ingredientFrame->setFrameShape( QFrame::StyledPanel );
+ ingredientFrame->setFrameShadow( QFrame::Raised );
+ ingredientFrameLayout = new QGridLayout( ingredientFrame, 1, 1, 3, 3, "ingredientFrameLayout");
+
+ QLabel *ingredientInfoLabel = new QLabel(i18n("Enter ingredients: (e.g. chicken pasta \"white wine\")"),ingredientFrame);
+ ingredientInfoLabel->setTextFormat( Qt::RichText );
+ ingredientFrameLayout->addMultiCellWidget( ingredientInfoLabel, 0, 0, 0, 1 );
+
+ ingredientsAllEdit = new QLineEdit( ingredientFrame, "ingredientsAllEdit" );
+
+ ingredientFrameLayout->addWidget( ingredientsAllEdit, 1, 1 );
+
+ ingredientsAnyEdit = new QLineEdit( ingredientFrame, "ingredientsAnyEdit" );
+
+ ingredientFrameLayout->addWidget( ingredientsAnyEdit, 2, 1 );
+
+ textLabel1_2 = new QLabel( ingredientFrame, "textLabel1_2" );
+
+ ingredientFrameLayout->addWidget( textLabel1_2, 2, 0 );
+
+ textLabel1 = new QLabel( ingredientFrame, "textLabel1" );
+
+ ingredientFrameLayout->addWidget( textLabel1, 1, 0 );
+
+ ingredientsWithoutEdit = new QLineEdit( ingredientFrame, "ingredientsWithoutEdit" );
+
+ ingredientFrameLayout->addWidget( ingredientsWithoutEdit, 3, 1 );
+
+ textLabel1_3 = new QLabel( ingredientFrame, "textLabel1_3" );
+
+ ingredientFrameLayout->addWidget( textLabel1_3, 3, 0 );
+ parametersFrameLayout->addWidget( ingredientFrame );
+ spacer3_2_3_2_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( spacer3_2_3_2_2 );
+
+
+ categoriesButton = new QPushButton( parametersFrame, "categoriesButton" );
+ categoriesButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( categoriesButton );
+
+ categoryFrame = new QFrame( parametersFrame, "categoryFrame" );
+ categoryFrame->setFrameShape( QFrame::StyledPanel );
+ categoryFrame->setFrameShadow( QFrame::Raised );
+ categoryFrameLayout = new QGridLayout( categoryFrame, 1, 1, 3, 3, "categoryFrameLayout");
+
+ QLabel *categoryInfoLabel = new QLabel(i18n("Enter categories: (e.g. Desserts Pastas \"Main Dishes\")"),categoryFrame);
+ categoryInfoLabel->setTextFormat( Qt::RichText );
+ categoryFrameLayout->addMultiCellWidget( categoryInfoLabel, 0, 0, 0, 1 );
+
+ categoriesAllEdit = new QLineEdit( categoryFrame, "categoriesAllEdit" );
+
+ categoryFrameLayout->addWidget( categoriesAllEdit, 1, 1 );
+
+ textLabel1_5 = new QLabel( categoryFrame, "textLabel1_5" );
+
+ categoryFrameLayout->addWidget( textLabel1_5, 1, 0 );
+
+ textLabel1_3_3 = new QLabel( categoryFrame, "textLabel1_3_3" );
+
+ categoryFrameLayout->addWidget( textLabel1_3_3, 3, 0 );
+
+ categoriesAnyEdit = new QLineEdit( categoryFrame, "categoriesAnyEdit" );
+
+ categoryFrameLayout->addWidget( categoriesAnyEdit, 2, 1 );
+
+ textLabel1_2_3 = new QLabel( categoryFrame, "textLabel1_2_3" );
+
+ categoryFrameLayout->addWidget( textLabel1_2_3, 2, 0 );
+
+ categoriesNotEdit = new QLineEdit( categoryFrame, "categoriesNotEdit" );
+
+ categoryFrameLayout->addWidget( categoriesNotEdit, 3, 1 );
+ parametersFrameLayout->addWidget( categoryFrame );
+ spacer3_2_3_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( spacer3_2_3_2 );
+
+
+ authorsButton = new QPushButton( parametersFrame, "authorsButton" );
+ authorsButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( authorsButton );
+
+ authorsFrame = new QFrame( parametersFrame, "authorsFrame" );
+ authorsFrame->setFrameShape( QFrame::StyledPanel );
+ authorsFrame->setFrameShadow( QFrame::Raised );
+ authorsFrameLayout = new QGridLayout( authorsFrame, 1, 1, 3, 3, "authorsFrameLayout");
+
+ QLabel *authorsInfoLabel = new QLabel(i18n("Enter author name (e.g. Smith or \"Jane Doe\")"),authorsFrame);
+ authorsInfoLabel->setTextFormat( Qt::RichText );
+ authorsFrameLayout->addMultiCellWidget( authorsInfoLabel, 0, 0, 0, 1 );
+
+ textLabel1_2_4 = new QLabel( authorsFrame, "textLabel1_2_4" );
+
+ authorsFrameLayout->addWidget( textLabel1_2_4, 1, 0 );
+
+ textLabel1_6 = new QLabel( authorsFrame, "textLabel1_6" );
+
+ authorsFrameLayout->addWidget( textLabel1_6, 2, 0 );
+
+ textLabel1_3_4 = new QLabel( authorsFrame, "textLabel1_3_4" );
+
+ authorsFrameLayout->addWidget( textLabel1_3_4, 3, 0 );
+
+ authorsAnyEdit = new QLineEdit( authorsFrame, "authorsAnyEdit" );
+
+ authorsFrameLayout->addWidget( authorsAnyEdit, 1, 1 );
+
+ authorsAllEdit = new QLineEdit( authorsFrame, "authorsAllEdit" );
+
+ authorsFrameLayout->addWidget( authorsAllEdit, 2, 1 );
+
+ authorsWithoutEdit = new QLineEdit( authorsFrame, "authorsWithoutEdit" );
+
+ authorsFrameLayout->addWidget( authorsWithoutEdit, 3, 1 );
+ parametersFrameLayout->addWidget( authorsFrame );
+ spacer3_2_3 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( spacer3_2_3 );
+
+
+ servingsButton = new QPushButton( parametersFrame, "servingsButton" );
+ servingsButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( servingsButton );
+
+ servingsFrame = new QFrame( parametersFrame, "servingsFrame" );
+ servingsFrame->setFrameShape( QFrame::StyledPanel );
+ servingsFrame->setFrameShadow( QFrame::Raised );
+ servingsFrameLayout = new QVBoxLayout( servingsFrame, 3, 3, "servingsFrameLayout");
+
+ enableServingsCheckBox = new QCheckBox( servingsFrame, "enableServingsCheckBox" );
+ servingsFrameLayout->addWidget( enableServingsCheckBox );
+
+ layout5 = new QHBoxLayout( 0, 0, 3, "layout5");
+
+ servingsComboBox = new QComboBox( FALSE, servingsFrame, "servingsComboBox" );
+ servingsComboBox->setEnabled( FALSE );
+ servingsComboBox->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 1, 0, servingsComboBox->sizePolicy().hasHeightForWidth() ) );
+ layout5->addWidget( servingsComboBox );
+
+ servingsSpinBox = new QSpinBox( servingsFrame, "servingsSpinBox" );
+ servingsSpinBox->setEnabled( FALSE );
+ servingsSpinBox->setMinValue( 1 );
+ servingsSpinBox->setMaxValue( 9999 );
+ layout5->addWidget( servingsSpinBox );
+ servingsFrameLayout->addLayout( layout5 );
+ parametersFrameLayout->addWidget( servingsFrame );
+ spacer3_2_2 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( spacer3_2_2 );
+
+
+ prepTimeButton = new QPushButton( parametersFrame, "prepTimeButton" );
+ prepTimeButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( prepTimeButton );
+
+ prepTimeFrame = new QFrame( parametersFrame, "prepTimeFrame" );
+ prepTimeFrame->setFrameShape( QFrame::StyledPanel );
+ prepTimeFrame->setFrameShadow( QFrame::Raised );
+ prepTimeFrameLayout = new QVBoxLayout( prepTimeFrame, 3, 3, "prepTimeFrameLayout");
+
+ enablePrepTimeCheckBox = new QCheckBox( prepTimeFrame, "enablePrepTimeCheckBox" );
+ prepTimeFrameLayout->addWidget( enablePrepTimeCheckBox );
+
+ layout6 = new QHBoxLayout( 0, 0, 3, "layout6");
+
+ prepTimeComboBox = new QComboBox( FALSE, prepTimeFrame, "prepTimeComboBox" );
+ prepTimeComboBox->setEnabled( FALSE );
+ prepTimeComboBox->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 1, 0, prepTimeComboBox->sizePolicy().hasHeightForWidth() ) );
+ layout6->addWidget( prepTimeComboBox );
+
+ prepTimeEdit = new QTimeEdit( prepTimeFrame, "prepTimeEdit" );
+ prepTimeEdit->setEnabled( FALSE );
+ prepTimeEdit->setDisplay( int( QTimeEdit::Minutes | QTimeEdit::Hours ) );
+ layout6->addWidget( prepTimeEdit );
+ prepTimeFrameLayout->addLayout( layout6 );
+ parametersFrameLayout->addWidget( prepTimeFrame );
+ spacer15 = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( spacer15 );
+
+
+ instructionsButton = new QPushButton( parametersFrame, "instructionsButton" );
+ instructionsButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( instructionsButton );
+
+ instructionsFrame = new QFrame( parametersFrame, "instructionsFrame" );
+ instructionsFrame->setFrameShape( QFrame::StyledPanel );
+ instructionsFrame->setFrameShadow( QFrame::Raised );
+ instructionsFrameLayout = new QVBoxLayout( instructionsFrame, 5, 3, "instructionsFrameLayout");
+
+ requireAllInstructions = new QCheckBox( i18n("Require All Words"), instructionsFrame );
+ instructionsFrameLayout->addWidget( requireAllInstructions );
+
+ QHBox *instructionsHBox = new QHBox(instructionsFrame);
+ /*QLabel *instructionsInfoLabel = */new QLabel(i18n("Keywords:"),instructionsHBox);
+
+ instructionsEdit = new QLineEdit( instructionsHBox, "instructionsEdit" );
+ instructionsFrameLayout->addWidget( instructionsHBox );
+
+ parametersFrameLayout->addWidget( instructionsFrame );
+ instructionsFrameSpacer = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( instructionsFrameSpacer );
+
+
+ metaDataButton = new QPushButton( parametersFrame, "metaDataButton" );
+ metaDataButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( metaDataButton );
+
+ metaDataFrame = new QFrame( parametersFrame, "metaDataFrame" );
+ metaDataFrame->setFrameShape( QFrame::StyledPanel );
+ metaDataFrame->setFrameShadow( QFrame::Raised );
+ metaDataFrameLayout = new QVBoxLayout( metaDataFrame, 5, 3, "metaDataFrameLayout");
+
+ QLabel *createdLabel = new QLabel( i18n("Created:"), metaDataFrame );
+ metaDataFrameLayout->addWidget( createdLabel );
+
+ QHBox *createdHBox = new QHBox(metaDataFrame);
+ createdStartDateEdit = new KDateEdit(createdHBox,"createdStartEdit");
+ createdStartDateEdit->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ (void)new QLabel(" - ",createdHBox);
+ createdEndDateEdit = new KDateEdit(createdHBox,"createdEndEdit");
+ createdEndDateEdit->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ metaDataFrameLayout->addWidget( createdHBox );
+
+ QLabel *modifiedLabel = new QLabel( i18n("Modified:"), metaDataFrame );
+ metaDataFrameLayout->addWidget( modifiedLabel );
+
+ QHBox *modifiedHBox = new QHBox(metaDataFrame);
+ modifiedStartDateEdit = new KDateEdit(modifiedHBox,"modifiedStartEdit");
+ modifiedStartDateEdit->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ (void)new QLabel(" - ",modifiedHBox);
+ modifiedEndDateEdit = new KDateEdit(modifiedHBox,"modifiedEndEdit");
+ modifiedEndDateEdit->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ metaDataFrameLayout->addWidget( modifiedHBox );
+
+ QLabel *accessedLabel = new QLabel( i18n("Last Accessed:"), metaDataFrame );
+ metaDataFrameLayout->addWidget( accessedLabel );
+
+ QHBox *accessedHBox = new QHBox(metaDataFrame);
+ accessedStartDateEdit = new KDateEdit(accessedHBox,"accessedStartEdit");
+ accessedStartDateEdit->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ (void)new QLabel(" - ",accessedHBox);
+ accessedEndDateEdit = new KDateEdit(accessedHBox,"accessedEndEdit");
+ accessedEndDateEdit->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ metaDataFrameLayout->addWidget( accessedHBox );
+
+ parametersFrameLayout->addWidget( metaDataFrame );
+ metaDataFrameSpacer = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred );
+ parametersFrameLayout->addItem( metaDataFrameSpacer );
+
+ //=============RATINGS FRAME===========//
+ ratingsButton = new QPushButton( parametersFrame, "ratingsButton" );
+ ratingsButton->setToggleButton( TRUE );
+ parametersFrameLayout->addWidget( ratingsButton );
+
+ ratingButtonGroup = new QButtonGroup( parametersFrame, "ratingButtonGroup" );
+ ratingButtonGroup->setLineWidth( 0 );
+ ratingButtonGroup->setColumnLayout(0, Qt::Vertical );
+ ratingButtonGroup->layout()->setSpacing( 5 );
+ ratingButtonGroup->layout()->setMargin( 3 );
+ ratingButtonGroupLayout = new QVBoxLayout( ratingButtonGroup->layout() );
+ ratingButtonGroupLayout->setAlignment( Qt::AlignTop );
+
+ ratingAvgRadioButton = new QRadioButton( ratingButtonGroup, "ratingAvgRadioButton" );
+ ratingAvgRadioButton->setChecked( TRUE );
+ ratingButtonGroupLayout->addWidget( ratingAvgRadioButton );
+
+ ratingAvgFrame = new QFrame( ratingButtonGroup, "ratingAvgFrame" );
+ ratingAvgFrame->setLineWidth( 0 );
+ ratingAvgFrameLayout = new QHBoxLayout( ratingAvgFrame, 2, 2, "ratingAvgFrameLayout");
+
+ avgStarsEdit = new FractionInput( ratingAvgFrame );
+ avgStarsEdit->setAllowRange(true);
+ ratingAvgFrameLayout->addWidget( avgStarsEdit );
+
+ avgStarsLabel = new QLabel( ratingAvgFrame, "avgStarsLabel" );
+ ratingAvgFrameLayout->addWidget( avgStarsLabel );
+ ratingButtonGroupLayout->addWidget( ratingAvgFrame );
+
+ criterionRadioButton = new QRadioButton( ratingButtonGroup, "criterionRadioButton" );
+ ratingButtonGroupLayout->addWidget( criterionRadioButton );
+
+ criterionFrame = new QFrame( ratingButtonGroup, "criterionFrame" );
+ criterionFrame->setEnabled( FALSE );
+ criterionFrame->setLineWidth( 0 );
+ criterionFrameLayout = new QVBoxLayout( criterionFrame, 2, 2, "criterionFrameLayout");
+
+ layout12 = new QHBoxLayout( 0, 0, 6, "layout12");
+
+ criteriaComboBox = new CriteriaComboBox( FALSE, criterionFrame, database );
+ criteriaComboBox->setEditable( false );
+ criteriaComboBox->reload();
+ layout12->addWidget( criteriaComboBox );
+
+ starsWidget = new FractionInput( criterionFrame );
+ starsWidget->setAllowRange(true);
+ starsWidget->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
+ layout12->addWidget( starsWidget );
+
+
+ addCriteriaButton = new QPushButton( criterionFrame, "addCriteriaButton" );
+ addCriteriaButton->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0, addCriteriaButton->sizePolicy().hasHeightForWidth() ) );
+ addCriteriaButton->setMaximumSize( QSize( 30, 30 ) );
+ layout12->addWidget( addCriteriaButton );
+#if 0
+ removeCriteriaButton = new QPushButton( criterionFrame, "removeCriteriaButton" );
+ removeCriteriaButton->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0, removeCriteriaButton->sizePolicy().hasHeightForWidth() ) );
+ removeCriteriaButton->setMaximumSize( QSize( 30, 30 ) );
+ layout12->addWidget( removeCriteriaButton );
+#endif
+ criterionFrameLayout->addLayout( layout12 );
+
+ criteriaListView = new KListView( criterionFrame, "criteriaListView" );
+ criteriaListView->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum );
+ criteriaListView->addColumn( i18n( "Criterion" ) );
+ criteriaListView->addColumn( i18n( "Stars" ) );
+ criterionFrameLayout->addWidget( criteriaListView );
+ ratingButtonGroupLayout->addWidget( criterionFrame );
+
+ parametersFrameLayout->addWidget( ratingButtonGroup );
+ ratingsFrameSpacer = new QSpacerItem( 0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ parametersFrameLayout->addItem( ratingsFrameSpacer );
+
+
+ scrollView1->addChild( parametersFrame );
+ layout7->addWidget( scrollView1 );
+
+ layout9 = new QHBoxLayout( 0, 0, 3, "layout9");
+
+ clearButton = new KPushButton( this, "clearButton" );
+ clearButton->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 0, 0, clearButton->sizePolicy().hasHeightForWidth() ) );
+ layout9->addWidget( clearButton );
+ spacer3 = new QSpacerItem( 110, 0, QSizePolicy::Expanding, QSizePolicy::Minimum );
+ layout9->addItem( spacer3 );
+
+ findButton = new KPushButton( this, "findButton" );
+ findButton->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 0, 0, findButton->sizePolicy().hasHeightForWidth() ) );
+ layout9->addWidget( findButton );
+ layout7->addLayout( layout9 );
+ AdvancedSearchDialogLayout->addLayout( layout7 );
+
+ resultsListView = new KListView( this, "resultsListView" );
+ resultsListView->setSelectionMode( QListView::Extended );
+ resultsListView->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)7, (QSizePolicy::SizeType)7, 0, 1, resultsListView->sizePolicy().hasHeightForWidth() ) );
+ AdvancedSearchDialogLayout->addWidget( resultsListView );
+ languageChange();
+ clearWState( WState_Polished );
+ ///
+ ///END OF AUTOMATICALLY GENERATED GUI CODE///
+ ///
+
+ resultsListView->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+ scrollView1->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored );
+
+ AdvancedSearchDialogLayout->setStretchFactor( resultsListView, 2 );
+
+ scrollView1->setHScrollBarMode( QScrollView::AlwaysOff );
+ scrollView1->setResizePolicy( QScrollView::AutoOneFit );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ resultsListView->addColumn( i18n( "Title" ) );
+ resultsListView->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ actionHandler = new RecipeActionsHandler( resultsListView, database, RecipeActionsHandler::Open | RecipeActionsHandler::Edit | RecipeActionsHandler::Export | RecipeActionsHandler::CopyToClipboard | RecipeActionsHandler::Remove );
+
+ connect( titleEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( ingredientsAllEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( ingredientsAnyEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( ingredientsWithoutEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( authorsAnyEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( authorsAllEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( authorsWithoutEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( categoriesNotEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( categoriesAnyEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( categoriesAllEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( ingredientsWithoutEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+ connect( instructionsEdit, SIGNAL( returnPressed() ), SLOT( search() ) );
+
+ connect( findButton, SIGNAL( clicked() ), SLOT( search() ) );
+ connect( clearButton, SIGNAL( clicked() ), SLOT( clear() ) );
+
+ connect( enableServingsCheckBox, SIGNAL( toggled( bool ) ), servingsSpinBox, SLOT( setEnabled( bool ) ) );
+ connect( enableServingsCheckBox, SIGNAL( toggled( bool ) ), servingsComboBox, SLOT( setEnabled( bool ) ) );
+ connect( enablePrepTimeCheckBox, SIGNAL( toggled( bool ) ), prepTimeEdit, SLOT( setEnabled( bool ) ) );
+ connect( enablePrepTimeCheckBox, SIGNAL( toggled( bool ) ), prepTimeComboBox, SLOT( setEnabled( bool ) ) );
+
+ connect( titleButton, SIGNAL( toggled( bool ) ), titleFrame, SLOT( setShown( bool ) ) );
+ connect( ingredientButton, SIGNAL( toggled( bool ) ), ingredientFrame, SLOT( setShown( bool ) ) );
+ connect( authorsButton, SIGNAL( toggled( bool ) ), authorsFrame, SLOT( setShown( bool ) ) );
+ connect( categoriesButton, SIGNAL( toggled( bool ) ), categoryFrame, SLOT( setShown( bool ) ) );
+ connect( servingsButton, SIGNAL( toggled( bool ) ), servingsFrame, SLOT( setShown( bool ) ) );
+ connect( prepTimeButton, SIGNAL( toggled( bool ) ), prepTimeFrame, SLOT( setShown( bool ) ) );
+ connect( instructionsButton, SIGNAL( toggled( bool ) ), instructionsFrame, SLOT( setShown( bool ) ) );
+ connect( metaDataButton, SIGNAL( toggled( bool ) ), metaDataFrame, SLOT( setShown( bool ) ) );
+ connect( ratingsButton, SIGNAL( toggled( bool ) ), ratingButtonGroup, SLOT( setShown( bool ) ) );
+
+ connect( titleButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( ingredientButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( authorsButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( categoriesButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( servingsButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( prepTimeButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( instructionsButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( metaDataButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+ connect( ratingsButton, SIGNAL( toggled( bool ) ), SLOT( buttonSwitched() ) );
+
+ connect( ratingButtonGroup, SIGNAL( clicked( int ) ), this, SLOT( activateRatingOption( int ) ) );
+
+ connect( addCriteriaButton, SIGNAL( clicked() ), this, SLOT( slotAddRatingCriteria() ) );
+
+ titleFrame->setShown(false);
+ ingredientFrame->setShown(false);
+ authorsFrame->setShown(false);
+ categoryFrame->setShown(false);
+ servingsFrame->setShown(false);
+ prepTimeFrame->setShown(false);
+ instructionsFrame->setShown(false);
+ metaDataFrame->setShown(false);
+ ratingButtonGroup->setShown(false);
+
+ connect( actionHandler, SIGNAL( recipeSelected( int, int ) ), SIGNAL( recipeSelected( int, int ) ) );
+ connect( actionHandler, SIGNAL( recipesSelected( const QValueList<int> &, int ) ), SIGNAL( recipesSelected( const QValueList<int> &, int ) ) );
+
+ connect( database, SIGNAL( recipeRemoved( int ) ), SLOT( removeRecipe( int ) ) );
+
+ clear();
+
+ KPopupMenu *kpop = new KPopupMenu( criteriaListView );
+ kpop->insertItem( i18n( "&Delete" ), this, SLOT( slotRemoveRatingCriteria() ), Key_Delete );
+}
+
+AdvancedSearchDialog::~AdvancedSearchDialog()
+{}
+
+void AdvancedSearchDialog::languageChange()
+{
+ titleButton->setText( QString("%1 >>").arg(i18n("Title")) );
+ textLabel1_4->setText( i18n( "Search using the following criteria:" ) );
+ ingredientButton->setText( QString("%1 >>").arg(i18n("Ingredients")) );
+ textLabel1_2->setText( i18n( "Uses any of:" ) );
+ textLabel1->setText( i18n( "Uses all:" ) );
+ textLabel1_3->setText( i18n( "Without:" ) );
+ categoriesButton->setText( i18n( "Categories >>" ) );
+ textLabel1_5->setText( i18n( "In all:" ) );
+ textLabel1_3_3->setText( i18n( "Not in:" ) );
+ textLabel1_2_3->setText( i18n( "In any of:" ) );
+ authorsButton->setText( QString("%1 >>").arg(i18n("Authors")) );
+ textLabel1_2_4->setText( i18n( "By any of:" ) );
+ textLabel1_6->setText( i18n( "By all:" ) );
+ textLabel1_3_4->setText( i18n( "Not by:" ) );
+ servingsButton->setText( QString("%1 >>").arg(i18n("Yield")) );
+ enableServingsCheckBox->setText( i18n( "Enabled" ) );
+ servingsComboBox->clear();
+ servingsComboBox->insertItem( i18n( "Yields at least:" ) );
+ servingsComboBox->insertItem( i18n( "Yields at most:" ) );
+ servingsComboBox->insertItem( i18n( "Yields about:" ) );
+ prepTimeButton->setText( QString("%1 >>").arg(i18n("Preparation Time")) );
+ enablePrepTimeCheckBox->setText( i18n( "Enabled" ) );
+ prepTimeComboBox->clear();
+ prepTimeComboBox->insertItem( i18n( "Ready in at most:" ) );
+ prepTimeComboBox->insertItem( i18n( "Ready in about:" ) );
+ instructionsButton->setText( QString("%1 >>").arg(i18n("Instructions")) );
+ metaDataButton->setText( QString("%1 >>").arg(i18n("Meta Data")) );
+ clearButton->setText( i18n( "C&lear" ) );
+ clearButton->setAccel( QKeySequence( i18n( "Alt+L" ) ) );
+ findButton->setText( i18n( "&Search" ) );
+ ratingAvgRadioButton->setText( i18n( "By average:" ) );
+ avgStarsLabel->setText( i18n( "stars" ) );
+ criterionRadioButton->setText( i18n( "By criteria:" ) );
+ addCriteriaButton->setText( i18n( "+" ) );
+ //removeCriteriaButton->setText( i18n( "-" ) );
+ criteriaListView->header()->setLabel( 0, i18n( "Criteria" ) );
+ criteriaListView->header()->setLabel( 1, i18n( "Stars" ) );
+ ratingsButton->setText( QString("%1 >>").arg(i18n("Ratings")) );
+}
+
+void AdvancedSearchDialog::removeRecipe( int id )
+{
+ QListViewItemIterator iterator( resultsListView );
+ while ( iterator.current() ) {
+ if ( iterator.current()->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current();
+ if ( recipe_it->recipeID() == id )
+ delete recipe_it;
+ }
+ ++iterator;
+ }
+}
+
+void AdvancedSearchDialog::clear()
+{
+ resultsListView->clear();
+ authorsAllEdit->clear();
+ authorsWithoutEdit->clear();
+ authorsAnyEdit->clear();
+ categoriesAllEdit->clear();
+ categoriesNotEdit->clear();
+ categoriesAnyEdit->clear();
+ ingredientsAllEdit->clear();
+ ingredientsWithoutEdit->clear();
+ ingredientsAnyEdit->clear();
+ titleEdit->clear();
+ instructionsEdit->clear();
+
+ createdStartDateEdit->setDate( QDate() );
+ createdEndDateEdit->setDate( QDate() );
+ modifiedStartDateEdit->setDate( QDate() );
+ modifiedEndDateEdit->setDate( QDate() );
+ accessedStartDateEdit->setDate( QDate() );
+ accessedEndDateEdit->setDate( QDate() );
+
+ servingsSpinBox->setValue( 1 );
+ prepTimeEdit->setTime( QTime(0,0) );
+
+ enablePrepTimeCheckBox->setChecked(false);
+ enableServingsCheckBox->setChecked(false);
+
+ requireAllTitle->setChecked(false);
+ requireAllInstructions->setChecked(false);
+
+ ratingAvgRadioButton->setChecked(true);
+ activateRatingOption(0);
+ avgStarsEdit->clear();
+ criteriaListView->clear();
+ starsWidget->clear();
+}
+
+void AdvancedSearchDialog::activateRatingOption( int button_id )
+{
+ switch ( button_id ) {
+ case 0:
+ criterionFrame->setEnabled( false );
+ ratingAvgFrame->setEnabled( true );
+ break;
+ case 1:
+ criterionFrame->setEnabled( true );
+ ratingAvgFrame->setEnabled( false );
+ break;
+ default:
+ break;
+ }
+}
+
+void AdvancedSearchDialog::buttonSwitched()
+{
+ const QObject *sent = sender();
+
+ if ( sent->inherits("QPushButton") ) {
+ QPushButton *pushed = (QPushButton*) sent;
+
+ QString suffix = ( pushed->state() == QButton::On ) ? " <<" : " >>";
+ pushed->setText( pushed->text().left( pushed->text().length() - 3 ) + suffix );
+ }
+}
+
+void AdvancedSearchDialog::search()
+{
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ //we need to load more than just the title because we'll be doing further refining of the search
+ int load_items = RecipeDB::Title | RecipeDB::NamesOnly | RecipeDB::Noatime;
+ if ( !authorsAllEdit->text().isEmpty() || !authorsWithoutEdit->text().isEmpty() )
+ load_items |= RecipeDB::Authors;
+ if ( !ingredientsAllEdit->text().isEmpty() || !ingredientsWithoutEdit->text().isEmpty() )
+ load_items |= RecipeDB::Ingredients;
+ if ( !categoriesAllEdit->text().isEmpty() || !categoriesNotEdit->text().isEmpty() )
+ load_items |= RecipeDB::Categories;
+ if ( (ratingAvgRadioButton->isChecked() && !avgStarsEdit->isEmpty()) || (criterionRadioButton->isChecked() && criteriaListView->firstChild()) )
+ load_items |= RecipeDB::Ratings;
+
+ RecipeSearchParameters parameters;
+
+ parameters.titleKeywords = split(titleEdit->text(),true);
+ parameters.requireAllTitleWords = requireAllTitle->isChecked();
+
+ parameters.instructionsKeywords = split(instructionsEdit->text(),true);
+ parameters.requireAllInstructionsWords = requireAllInstructions->isChecked();
+
+ parameters.ingsOr = split(ingredientsAnyEdit->text(),true);
+ parameters.catsOr = split(categoriesAnyEdit->text(),true);
+ parameters.authorsOr = split(authorsAnyEdit->text(),true);
+
+ if ( enablePrepTimeCheckBox->isChecked() )
+ parameters.prep_time = prepTimeEdit->time();
+ parameters.prep_param = prepTimeComboBox->currentItem();
+
+ if ( enableServingsCheckBox->isChecked() )
+ parameters.servings = servingsSpinBox->value();
+ parameters.servings_param = servingsComboBox->currentItem();
+
+ parameters.createdDateBegin = createdStartDateEdit->date();
+ parameters.createdDateEnd = createdEndDateEdit->date();
+ if ( parameters.createdDateEnd.date().isValid() )
+ parameters.createdDateEnd = parameters.createdDateEnd.addDays(1); //we want to include the given day in the search
+
+ parameters.modifiedDateBegin = modifiedStartDateEdit->date();
+ parameters.modifiedDateEnd = modifiedEndDateEdit->date();
+ if ( parameters.modifiedDateEnd.date().isValid() )
+ parameters.modifiedDateEnd = parameters.modifiedDateEnd.addDays(1); //we want to include the given day in the search
+
+ parameters.accessedDateBegin = accessedStartDateEdit->date();
+ parameters.accessedDateEnd = accessedEndDateEdit->date();
+ if ( parameters.accessedDateEnd.date().isValid() )
+ parameters.accessedDateEnd = parameters.accessedDateEnd.addDays(1); //we want to include the given day in the search
+
+ START_TIMER("Doing database SQL search");
+ RecipeList allRecipes;
+ database->search( &allRecipes, load_items, parameters );
+ END_TIMER();
+
+ /*
+ * Ideally, would be done by the above SQL query, but I have no idea how to accomplish this.
+ */
+
+ START_TIMER("Further narrowing the search (no SQL)");
+ QStringList items = split(authorsAllEdit->text());
+ for ( QStringList::const_iterator author_it = items.begin(); author_it != items.end(); ++author_it ) {
+ for ( RecipeList::iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ if ( ( *it ).authorList.findByName( QRegExp(*author_it,false, true) ).id == -1 ) {
+ it = allRecipes.remove( it );
+ it--;
+ }
+ }
+ }
+ items = split(authorsWithoutEdit->text());
+ for ( QStringList::const_iterator author_it = items.begin(); author_it != items.end(); ++author_it ) {
+ for ( RecipeList::iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ if ( ( *it ).authorList.findByName( QRegExp(*author_it,false,true) ).id != -1 ) {
+ it = allRecipes.remove( it );
+ it--;
+ }
+ }
+ }
+
+ //narrow down by categories
+ items = split(categoriesAllEdit->text());
+ for ( QStringList::const_iterator cat_it = items.begin(); cat_it != items.end(); ++cat_it ) {
+ for ( RecipeList::iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ if ( ( *it ).categoryList.findByName( QRegExp(*cat_it,false,true) ).id == -1 ) {
+ it = allRecipes.remove( it );
+ it--;
+ }
+ }
+ }
+ items = split(categoriesNotEdit->text());
+ for ( QStringList::const_iterator cat_it = items.begin(); cat_it != items.end(); ++cat_it ) {
+ for ( RecipeList::iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ if ( ( *it ).categoryList.findByName( QRegExp(*cat_it,false,true) ).id != -1 ) {
+ it = allRecipes.remove( it );
+ it--;
+ }
+ }
+ }
+
+ //narrow down by ingredients
+ items = split(ingredientsAllEdit->text());
+ for ( QStringList::const_iterator ing_it = items.begin(); ing_it != items.end(); ++ing_it ) {
+ for ( RecipeList::iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ if ( ( *it ).ingList.findByName( QRegExp(*ing_it,false,true) ).ingredientID == -1 ) {
+ it = allRecipes.remove( it );
+ it--;
+ }
+ }
+ }
+ items = split(ingredientsWithoutEdit->text());
+ for ( QStringList::const_iterator ing_it = items.begin(); ing_it != items.end(); ++ing_it ) {
+ for ( RecipeList::iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ if ( ( *it ).ingList.findByName( QRegExp(*ing_it,false,true) ).ingredientID != -1 ) {
+ it = allRecipes.remove( it );
+ it--;
+ }
+ }
+ }
+
+ if ( ratingAvgRadioButton->isChecked() && !avgStarsEdit->isEmpty() ) {
+ for ( RecipeList::iterator recipe_it = allRecipes.begin(); recipe_it != allRecipes.end(); ++recipe_it ) {
+ double sum = 0;
+ int count = 0;
+
+ for ( RatingList::iterator rating_it = (*recipe_it).ratingList.begin(); rating_it != (*recipe_it).ratingList.end(); ++rating_it ) {
+ sum += (*rating_it).average();
+ ++count;
+ }
+
+ if ( count != 0 ) {
+ double average = sum/count;
+
+ double stars;
+ double stars_offset;
+ avgStarsEdit->value(stars,stars_offset);
+ if ( stars_offset < 1e-10 ) { //if an exact amount is given, search for an amount within 0.5 of what is given
+ //we can get negatives here, but it really doesn't matter
+ stars = stars-0.5;
+ stars_offset = 1.0;
+ }
+
+
+ kdDebug()<<"average for "<<(*recipe_it).title<<" "<<average<<endl;
+ if ( average < stars || average > stars + stars_offset ) {
+ recipe_it = allRecipes.remove( recipe_it );
+ recipe_it--;
+ }
+ }
+ else {
+ recipe_it = allRecipes.remove( recipe_it );
+ recipe_it--;
+ }
+ }
+ }
+
+ //TODO: Clean this up and/or do it more efficiently
+ if ( criterionRadioButton->isChecked() && criteriaListView->firstChild() ) {
+ for ( RecipeList::iterator recipe_it = allRecipes.begin(); recipe_it != allRecipes.end(); ++recipe_it ) {
+ QMap< int, double > idSumMap;
+ QMap< int, int > idCountMap;
+
+ for ( RatingList::const_iterator rating_it = (*recipe_it).ratingList.begin(); rating_it != (*recipe_it).ratingList.end(); ++rating_it ) {
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ QMap< int, double >::iterator sum_it = idSumMap.find((*rc_it).id);
+ if ( sum_it == idSumMap.end() )
+ sum_it = idSumMap.insert((*rc_it).id,0);
+ (*sum_it) += (*rc_it).stars;
+
+ QMap< int, int >::iterator count_it = idCountMap.find((*rc_it).id);
+ if ( count_it == idCountMap.end() )
+ count_it = idCountMap.insert((*rc_it).id,0);
+ (*count_it)++;
+ }
+ }
+
+ for ( QListViewItem *item = criteriaListView->firstChild(); item; item = item->nextSibling() ) {
+ Ingredient i; i.setAmount( item->text(1) );
+ double stars = i.amount;
+ double stars_offset = i.amount_offset;
+
+ if ( stars_offset < 1e-10 ) { //if an exact amount is given, search for an amount within 0.5 of what is given
+ //we can get negatives here, but it really doesn't matter
+ stars = stars-0.5;
+ stars_offset = 1.0;
+ }
+
+ int id = item->text(2).toInt();
+
+ QMap< int, double >::iterator sum_it = idSumMap.find(id);
+ if ( sum_it != idSumMap.end() ) {
+ QMap< int, int >::iterator count_it = idCountMap.find(id);
+ double average = (*sum_it)/(*count_it);
+
+ if ( average < stars || average > stars + stars_offset ) {
+ recipe_it = allRecipes.remove( recipe_it );
+ recipe_it--;
+ break;
+ }
+ }
+ else {
+ recipe_it = allRecipes.remove( recipe_it );
+ recipe_it--;
+ break;
+ }
+ }
+ }
+ }
+ END_TIMER();
+
+
+ //now display the recipes left
+ resultsListView->clear();
+ for ( RecipeList::const_iterator it = allRecipes.begin(); it != allRecipes.end(); ++it ) {
+ ( void ) new RecipeListItem( resultsListView, *it );
+ }
+
+ if ( !resultsListView->firstChild() ) {
+ ( void ) new QListViewItem( resultsListView, "--- "+i18n("No matching recipes found")+" ---");
+ }
+
+ KApplication::restoreOverrideCursor();
+}
+
+QStringList AdvancedSearchDialog::split( const QString &text, bool sql_wildcards ) const
+{
+ QStringList result;
+
+ // To keep quoted words together, first split on quotes,
+ // and then split again on the even numbered items
+
+ QStringList temp = QStringList::split('"',text,true);
+ for ( uint i = 0; i < temp.count(); ++i ) {
+ if ( i & 1 ) //odd
+ result += temp[i].stripWhiteSpace();
+ else //even
+ result += QStringList::split(' ',temp[i]);
+ }
+
+ if ( sql_wildcards ) {
+ for ( QStringList::iterator it = result.begin(); it != result.end(); ++it ) {
+ (*it).replace("%","\\%");
+ (*it).replace("_","\\_");
+
+ (*it).replace("*","%");
+ (*it).replace("?","_");
+ }
+ }
+
+ return result;
+}
+
+void AdvancedSearchDialog::slotAddRatingCriteria()
+{
+ QListViewItem * it = new QListViewItem(criteriaListView,criteriaComboBox->currentText());
+
+ MixedNumber stars;
+ double stars_offset;
+ starsWidget->value(stars,stars_offset);
+ QString stars_str = stars.toString();
+ if ( stars_offset > 0 )
+ stars_str += "-"+MixedNumber( stars + stars_offset ).toString();
+ else if ( stars.toDouble() <= 1e-10 )
+ stars_str = "";
+
+ it->setText(1,stars_str);
+ it->setText(2,QString::number(criteriaComboBox->criteriaID(criteriaComboBox->currentItem())));
+}
+
+void AdvancedSearchDialog::slotRemoveRatingCriteria()
+{
+ delete criteriaListView->selectedItem();
+}
+
+#include "advancedsearchdialog.moc"
diff --git a/krecipes/src/dialogs/advancedsearchdialog.h b/krecipes/src/dialogs/advancedsearchdialog.h
new file mode 100644
index 0000000..e6e88df
--- /dev/null
+++ b/krecipes/src/dialogs/advancedsearchdialog.h
@@ -0,0 +1,179 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef ADVANCEDSEARCHDIALOG_H
+#define ADVANCEDSEARCHDIALOG_H
+
+#include <qwidget.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QScrollView;
+class QPushButton;
+class QFrame;
+class QLineEdit;
+class QLabel;
+class QCheckBox;
+class QComboBox;
+class QSpinBox;
+class QTimeEdit;
+class KPushButton;
+class KListView;
+class QListViewItem;
+class QRadioButton;
+class QButtonGroup;
+
+class KDateEdit;
+class RecipeDB;
+class RecipeActionsHandler;
+class FractionInput;
+class CriteriaComboBox;
+
+
+class AdvancedSearchDialog : public QWidget
+{
+ Q_OBJECT
+
+public:
+ AdvancedSearchDialog( QWidget *parent, RecipeDB * );
+ ~AdvancedSearchDialog();
+
+ virtual void languageChange();
+
+ RecipeActionsHandler *actionHandler;
+
+protected:
+ QLabel* textLabel1_4;
+ QScrollView* scrollView1;
+ QFrame* parametersFrame;
+ QLineEdit* titleEdit;
+ QPushButton* titleButton;
+ QFrame* titleFrame;
+ QPushButton* ingredientButton;
+ QFrame* ingredientFrame;
+ QLineEdit* ingredientsAllEdit;
+ QLineEdit* ingredientsAnyEdit;
+ QLabel* textLabel1_2;
+ QLabel* textLabel1;
+ QLineEdit* ingredientsWithoutEdit;
+ QLabel* textLabel1_3;
+ QPushButton* categoriesButton;
+ QFrame* categoryFrame;
+ QLineEdit* categoriesAllEdit;
+ QLabel* textLabel1_5;
+ QLabel* textLabel1_3_3;
+ QLineEdit* categoriesAnyEdit;
+ QLabel* textLabel1_2_3;
+ QLineEdit* categoriesNotEdit;
+ QPushButton* authorsButton;
+ QFrame* authorsFrame;
+ QLabel* textLabel1_2_4;
+ QLabel* textLabel1_6;
+ QLabel* textLabel1_3_4;
+ QLineEdit* authorsAnyEdit;
+ QLineEdit* authorsAllEdit;
+ QLineEdit* authorsWithoutEdit;
+ QPushButton* servingsButton;
+ QFrame* servingsFrame;
+ QCheckBox* enableServingsCheckBox;
+ QComboBox* servingsComboBox;
+ QSpinBox* servingsSpinBox;
+ QPushButton* prepTimeButton;
+ QFrame* prepTimeFrame;
+ QCheckBox* enablePrepTimeCheckBox;
+ QComboBox* prepTimeComboBox;
+ QTimeEdit* prepTimeEdit;
+ QLineEdit* instructionsEdit;
+ QPushButton* instructionsButton;
+ QFrame* instructionsFrame;
+ KPushButton* clearButton;
+ KPushButton* findButton;
+ KListView* resultsListView;
+ QCheckBox *requireAllTitle;
+ QCheckBox *requireAllInstructions;
+ KDateEdit *createdStartDateEdit;
+ KDateEdit *createdEndDateEdit;
+ KDateEdit *modifiedStartDateEdit;
+ KDateEdit *modifiedEndDateEdit;
+ KDateEdit *accessedStartDateEdit;
+ KDateEdit *accessedEndDateEdit;
+ QPushButton* metaDataButton;
+ QFrame* metaDataFrame;
+ QRadioButton* ratingAvgRadioButton;
+ FractionInput* avgStarsEdit;
+ QLabel* avgStarsLabel;
+ QRadioButton* criterionRadioButton;
+ CriteriaComboBox* criteriaComboBox;
+ FractionInput* starsWidget;
+ QPushButton* addCriteriaButton;
+ QPushButton* removeCriteriaButton;
+ KListView* criteriaListView;
+ QPushButton* ratingsButton;
+ QButtonGroup *ratingButtonGroup;
+ QFrame *ratingAvgFrame;
+ QFrame *criterionFrame;
+ QVBoxLayout *criterionFrameLayout;
+ QHBoxLayout *ratingAvgFrameLayout;
+ QVBoxLayout *ratingButtonGroupLayout;
+
+ QHBoxLayout* AdvancedSearchDialogLayout;
+ QVBoxLayout* layout7;
+ QHBoxLayout* scrollView1Layout;
+ QVBoxLayout* parametersFrameLayout;
+ QSpacerItem* spacer3_2_3_2_2;
+ QSpacerItem* spacer3_2_3_2;
+ QSpacerItem* spacer3_2_3;
+ QSpacerItem* spacer3_2_2;
+ QSpacerItem* titleFrameSpacer;
+ QSpacerItem* instructionsFrameSpacer;
+ QSpacerItem* metaDataFrameSpacer;
+ QSpacerItem* spacer15;
+ QVBoxLayout* titleFrameLayout;
+ QGridLayout* ingredientFrameLayout;
+ QGridLayout* categoryFrameLayout;
+ QGridLayout* authorsFrameLayout;
+ QVBoxLayout* servingsFrameLayout;
+ QHBoxLayout* layout5;
+ QVBoxLayout* prepTimeFrameLayout;
+ QVBoxLayout* instructionsFrameLayout;
+ QVBoxLayout* metaDataFrameLayout;
+ QHBoxLayout* layout6;
+ QHBoxLayout* layout9;
+ QSpacerItem* spacer3;
+ QVBoxLayout* ratingsFrameLayout;
+ QHBoxLayout* layout11;
+ QHBoxLayout* layout12;
+ QSpacerItem* ratingsFrameSpacer;
+
+ RecipeDB *database;
+
+signals:
+ void recipeSelected( int, int );
+ void recipesSelected( const QValueList<int> &, int );
+
+private slots:
+ void search();
+ void clear();
+ void buttonSwitched();
+ void activateRatingOption( int button_id );
+ void slotAddRatingCriteria();
+ void slotRemoveRatingCriteria();
+
+ //called by a signal from the database when a recipe is removed
+ void removeRecipe( int id );
+
+private:
+ QStringList split( const QString &text, bool sql_wildcards = false ) const;
+};
+
+#endif //ADVANCEDSEARCHDIALOG_H
+
diff --git a/krecipes/src/dialogs/authorsdialog.cpp b/krecipes/src/dialogs/authorsdialog.cpp
new file mode 100644
index 0000000..8015f3f
--- /dev/null
+++ b/krecipes/src/dialogs/authorsdialog.cpp
@@ -0,0 +1,66 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "authorsdialog.h"
+#include "createelementdialog.h"
+#include "backends/recipedb.h"
+#include "widgets/authorlistview.h"
+
+#include <kdialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+AuthorsDialog::AuthorsDialog( QWidget* parent, RecipeDB *db ) : QWidget( parent )
+{
+
+ // Store pointer to database
+ database = db;
+
+ QHBoxLayout* layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ //Author List
+ authorListView = new StdAuthorListView( this, database, true );
+ layout->addWidget( authorListView );
+
+ //Buttons
+ QVBoxLayout* vboxl = new QVBoxLayout( KDialog::spacingHint() );
+
+ newAuthorButton = new QPushButton( this );
+ newAuthorButton->setText( i18n( "Create ..." ) );
+ newAuthorButton->setFlat( true );
+ vboxl->addWidget( newAuthorButton );
+
+ removeAuthorButton = new QPushButton( this );
+ removeAuthorButton->setText( i18n( "Delete" ) );
+ removeAuthorButton->setFlat( true );
+ vboxl->addWidget( removeAuthorButton );
+ vboxl->addStretch();
+
+ layout->addLayout( vboxl );
+
+ //Connect Signals & Slots
+
+ connect ( newAuthorButton, SIGNAL( clicked() ), authorListView, SLOT( createNew() ) );
+ connect ( removeAuthorButton, SIGNAL( clicked() ), authorListView, SLOT( remove
+ () ) );
+}
+
+AuthorsDialog::~AuthorsDialog()
+{}
+
+// (Re)loads the data from the database
+void AuthorsDialog::reload( ReloadFlags flag )
+{
+ authorListView->reload( flag );
+}
+
+#include "authorsdialog.moc"
diff --git a/krecipes/src/dialogs/authorsdialog.h b/krecipes/src/dialogs/authorsdialog.h
new file mode 100644
index 0000000..c7026cc
--- /dev/null
+++ b/krecipes/src/dialogs/authorsdialog.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef AUTHORSDIALOG_H
+#define AUTHORSDIALOG_H
+
+#include <qwidget.h>
+#include <qpushbutton.h>
+#include <qhbox.h>
+#include <qlayout.h>
+#include <kiconloader.h>
+#include <klistview.h>
+
+#include "widgets/dblistviewbase.h"
+
+class RecipeDB;
+class StdAuthorListView;
+
+/**
+@author Unai Garro
+*/
+
+class AuthorsDialog: public QWidget
+{
+
+ Q_OBJECT
+
+public:
+
+ AuthorsDialog( QWidget* parent, RecipeDB *db );
+ ~AuthorsDialog();
+ void reload( ReloadFlags flag = Load );
+private:
+ // Internal data
+ RecipeDB *database;
+ //Widgets
+ StdAuthorListView *authorListView;
+ QPushButton *newAuthorButton;
+ QPushButton *removeAuthorButton;
+ KIconLoader *il;
+};
+#endif
diff --git a/krecipes/src/dialogs/borderdialog.cpp b/krecipes/src/dialogs/borderdialog.cpp
new file mode 100644
index 0000000..c3d8971
--- /dev/null
+++ b/krecipes/src/dialogs/borderdialog.cpp
@@ -0,0 +1,261 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "borderdialog.h"
+
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qvbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+#include <kdebug.h>
+#include <khtml_part.h>
+#include <khtmlview.h>
+#include <klistbox.h>
+#include <klocale.h>
+
+#include "datablocks/kreborder.h"
+
+BorderDialog::BorderDialog( const KreBorder &border, QWidget* parent, const char* name )
+ : KDialogBase( parent, name, true, QString::null,
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ borderGroupBox = new QGroupBox( page, "borderGroupBox" );
+ borderGroupBox->setColumnLayout( 0, Qt::Vertical );
+ borderGroupBox->layout() ->setSpacing( 6 );
+ borderGroupBox->layout() ->setMargin( 11 );
+ borderGroupBoxLayout = new QVBoxLayout( borderGroupBox->layout() );
+ borderGroupBoxLayout->setAlignment( Qt::AlignTop );
+
+ layout4 = new QHBoxLayout( 0, 0, 6, "layout4" );
+
+ layout3 = new QVBoxLayout( 0, 0, 6, "layout3" );
+
+ styleLabel = new QLabel( borderGroupBox, "styleLabel" );
+ layout3->addWidget( styleLabel );
+
+ styleListBox = new KListBox( borderGroupBox, "styleListBox" );
+ layout3->addWidget( styleListBox );
+ layout4->addLayout( layout3 );
+
+ layout2 = new QVBoxLayout( 0, 0, 6, "layout2" );
+
+ colorLabel = new QLabel( borderGroupBox, "colorLabel" );
+ layout2->addWidget( colorLabel );
+
+ QHBox *color_hbox = new QHBox( borderGroupBox );
+ color_hbox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
+ hsSelector = new KHSSelector( color_hbox );
+ hsSelector->setMinimumSize( 140, 70 );
+ connect( hsSelector, SIGNAL( valueChanged( int, int ) ), SLOT( slotHSChanged( int, int ) ) );
+
+ valuePal = new KValueSelector( color_hbox );
+ valuePal->setMinimumSize( 26, 70 );
+ connect( valuePal, SIGNAL( valueChanged( int ) ), SLOT( slotVChanged( int ) ) );
+
+ layout2->addWidget( color_hbox );
+ layout4->addLayout( layout2 );
+
+ layout1 = new QVBoxLayout( 0, 0, 6, "layout1" );
+
+ widthLabel = new QLabel( borderGroupBox, "widthLabel" );
+ layout1->addWidget( widthLabel );
+
+ widthSpinBox = new QSpinBox( borderGroupBox, "widthSpinBox" );
+ widthSpinBox->setMinValue( 1 );
+ layout1->addWidget( widthSpinBox );
+
+ widthListBox = new KListBox( borderGroupBox, "widthListBox" );
+ layout1->addWidget( widthListBox );
+ layout4->addLayout( layout1 );
+ borderGroupBoxLayout->addLayout( layout4 );
+
+ borderPreview = new KHTMLPart( borderGroupBox );
+ borderPreview->view() ->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
+ borderGroupBoxLayout->addWidget( borderPreview->view() );
+
+ languageChange();
+
+ connect( widthSpinBox, SIGNAL( valueChanged( int ) ), SLOT( updatePreview() ) );
+ connect( widthListBox, SIGNAL( highlighted( int ) ), SLOT( updateSpinBox( int ) ) );
+ connect( styleListBox, SIGNAL( highlighted( int ) ), SLOT( updatePreview() ) );
+
+ initListBoxs();
+ loadBorder( border );
+
+ clearWState( WState_Polished );
+}
+
+BorderDialog::~BorderDialog()
+{}
+
+void BorderDialog::languageChange()
+{
+ borderGroupBox->setTitle( i18n( "Requested Border" ) );
+ styleLabel->setText( i18n( "Style:" ) );
+ colorLabel->setText( i18n( "Color:" ) );
+ widthLabel->setText( i18n( "Width:" ) );
+}
+
+KreBorder BorderDialog::border() const
+{
+ int width = widthSpinBox->value();
+
+ QString style;
+ switch ( styleListBox->currentItem() ) {
+ case 0:
+ style = "none";
+ break;
+ case 1:
+ style = "dotted";
+ break;
+ case 2:
+ style = "dashed";
+ break;
+ case 3:
+ style = "solid";
+ break;
+ case 4:
+ style = "double";
+ break;
+ case 5:
+ style = "groove";
+ break;
+ case 6:
+ style = "ridge";
+ break;
+ case 7:
+ style = "inset";
+ break;
+ case 8:
+ style = "outset";
+ break;
+ }
+
+ return KreBorder( width, style, selColor );
+}
+
+void BorderDialog::loadBorder( const KreBorder &border )
+{
+ widthSpinBox->setValue( border.width );
+ widthListBox->setCurrentItem( border.width - 1 );
+
+ if ( border.style == "none" )
+ styleListBox->setCurrentItem( 0 );
+ else if ( border.style == "dotted" )
+ styleListBox->setCurrentItem( 1 );
+ else if ( border.style == "dashed" )
+ styleListBox->setCurrentItem( 2 );
+ else if ( border.style == "solid" )
+ styleListBox->setCurrentItem( 3 );
+ else if ( border.style == "double" )
+ styleListBox->setCurrentItem( 4 );
+ else if ( border.style == "groove" )
+ styleListBox->setCurrentItem( 5 );
+ else if ( border.style == "ridge" )
+ styleListBox->setCurrentItem( 6 );
+ else if ( border.style == "inset" )
+ styleListBox->setCurrentItem( 7 );
+ else if ( border.style == "outset" )
+ styleListBox->setCurrentItem( 8 );
+
+ setColor( border.color );
+
+ updatePreview();
+}
+
+void BorderDialog::initListBoxs()
+{
+ styleListBox->insertItem( i18n( "None" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Dotted" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Dashed" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Solid" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Double" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Groove" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Ridge" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Inset" ) );
+ styleListBox->insertItem( i18n( "See http://krecipes.sourceforge.net/bordertypes.png for an example", "Outset" ) );
+
+ widthListBox->insertItem( "1" );
+ widthListBox->insertItem( "2" );
+ widthListBox->insertItem( "3" );
+ widthListBox->insertItem( "4" );
+ widthListBox->insertItem( "5" );
+ widthListBox->insertItem( "6" );
+ widthListBox->insertItem( "7" );
+}
+
+void BorderDialog::updatePreview()
+{
+ KreBorder b( border() );
+
+ QString html_str = QString( "<html><body><div style=\"vertical-align: middle; border: %1px %2 %3;\"><center><h1>%4</h1></center></div></body></html>" ).arg( b.width ).arg( b.style ).arg( b.color.name() ).arg( i18n( "Border Preview" ) );
+
+ borderPreview->begin();
+ borderPreview->write( html_str );
+ borderPreview->end();
+ borderPreview->show();
+}
+
+void BorderDialog::updateSpinBox( int index )
+{
+ widthSpinBox->setValue( index + 1 );
+}
+
+void BorderDialog::slotHSChanged( int h, int s )
+{
+ int _h, _s, v;
+ selColor.hsv( &_h, &_s, &v );
+ if ( v < 1 )
+ v = 1;
+
+ KColor col;
+ col.setHsv( h, s, v );
+
+ setColor( col );
+ updatePreview();
+}
+
+void BorderDialog::slotVChanged( int v )
+{
+ int h, s, _v;
+ selColor.hsv( &h, &s, &_v );
+
+ KColor col;
+ col.setHsv( h, s, v );
+
+ setColor( col );
+ updatePreview();
+}
+
+void BorderDialog::setColor( const KColor &color )
+{
+ if ( color == selColor )
+ return ;
+
+ selColor = color;
+
+ int h, s, v;
+ color.hsv( &h, &s, &v );
+ hsSelector->setValues( h, s );
+ valuePal->setHue( h );
+ valuePal->setSaturation( s );
+ valuePal->setValue( v );
+ valuePal->updateContents();
+ valuePal->repaint( FALSE );
+}
+
+#include "borderdialog.moc"
diff --git a/krecipes/src/dialogs/borderdialog.h b/krecipes/src/dialogs/borderdialog.h
new file mode 100644
index 0000000..2b35f01
--- /dev/null
+++ b/krecipes/src/dialogs/borderdialog.h
@@ -0,0 +1,75 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef BORDERDIALOG_H
+#define BORDERDIALOG_H
+
+#include <kdialogbase.h>
+#include <kcolordialog.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QGroupBox;
+class QLabel;
+class KListBox;
+class QListBoxItem;
+class QSpinBox;
+class KHTMLPart;
+
+class KreBorder;
+
+class BorderDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ BorderDialog( const KreBorder &border, QWidget* parent = 0, const char* name = 0 );
+ ~BorderDialog();
+
+ KreBorder border() const;
+
+ QGroupBox* borderGroupBox;
+ QLabel* styleLabel;
+ KListBox* styleListBox;
+ QLabel* colorLabel;
+ KHSSelector* hsSelector;
+ KValueSelector* valuePal;
+ QLabel* widthLabel;
+ QSpinBox* widthSpinBox;
+ KListBox* widthListBox;
+ KHTMLPart* borderPreview;
+
+protected:
+ QVBoxLayout* borderGroupBoxLayout;
+ QHBoxLayout* layout4;
+ QVBoxLayout* layout3;
+ QVBoxLayout* layout2;
+ QVBoxLayout* layout1;
+
+protected slots:
+ virtual void languageChange();
+ void updatePreview();
+ void updateSpinBox( int index );
+
+ void slotHSChanged( int h, int s );
+ void slotVChanged( int v );
+ void setColor( const KColor &color );
+
+private:
+ void loadBorder( const KreBorder &border );
+ void initListBoxs();
+
+ KColor selColor;
+
+};
+
+#endif // BORDERDIALOG_H
diff --git a/krecipes/src/dialogs/categorieseditordialog.cpp b/krecipes/src/dialogs/categorieseditordialog.cpp
new file mode 100644
index 0000000..4c767a6
--- /dev/null
+++ b/krecipes/src/dialogs/categorieseditordialog.cpp
@@ -0,0 +1,67 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "categorieseditordialog.h"
+
+#include <kdebug.h>
+#include <kdialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include "widgets/categorylistview.h"
+#include "createcategorydialog.h"
+#include "backends/recipedb.h"
+
+CategoriesEditorDialog::CategoriesEditorDialog( QWidget* parent, RecipeDB *db ) : QWidget( parent )
+{
+
+ // Store pointer to database
+ database = db;
+
+ QHBoxLayout* layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ //Category List
+ categoryListView = new StdCategoryListView( this, database, true );
+ layout->addWidget( categoryListView );
+
+ //Buttons
+ QVBoxLayout* vboxl = new QVBoxLayout( KDialog::spacingHint() );
+
+ newCategoryButton = new QPushButton( this );
+ newCategoryButton->setText( i18n( "Create ..." ) );
+ newCategoryButton->setFlat( true );
+ vboxl->addWidget( newCategoryButton );
+
+ removeCategoryButton = new QPushButton( this );
+ removeCategoryButton->setText( i18n( "Delete" ) );
+ removeCategoryButton->setFlat( true );
+ vboxl->addWidget( removeCategoryButton );
+ vboxl->addStretch();
+
+ layout->addLayout( vboxl );
+
+ //Connect Signals & Slots
+
+ connect ( newCategoryButton, SIGNAL( clicked() ), categoryListView, SLOT( createNew() ) );
+ connect ( removeCategoryButton, SIGNAL( clicked() ), categoryListView, SLOT( remove
+ () ) );
+}
+
+CategoriesEditorDialog::~CategoriesEditorDialog()
+{}
+
+void CategoriesEditorDialog::reload( ReloadFlags flag )
+{
+ categoryListView->reload( flag );
+}
+
+#include "categorieseditordialog.moc"
diff --git a/krecipes/src/dialogs/categorieseditordialog.h b/krecipes/src/dialogs/categorieseditordialog.h
new file mode 100644
index 0000000..6b8ced9
--- /dev/null
+++ b/krecipes/src/dialogs/categorieseditordialog.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CATEGORIESEDITORDIALOG_H
+#define CATEGORIESEDITORDIALOG_H
+
+#include <qwidget.h>
+#include <qpushbutton.h>
+#include <qhbox.h>
+#include <qlayout.h>
+#include <kiconloader.h>
+#include <klistview.h>
+
+#include "datablocks/categorytree.h"
+#include "widgets/dblistviewbase.h"
+
+class RecipeDB;
+class StdCategoryListView;
+
+/**
+@author Unai Garro
+*/
+class CategoriesEditorDialog: public QWidget
+{
+
+ Q_OBJECT
+
+public:
+
+ CategoriesEditorDialog( QWidget* parent, RecipeDB *db );
+ ~CategoriesEditorDialog();
+
+ void reload( ReloadFlags flag = Load );
+
+private:
+ // Internal data
+ RecipeDB *database;
+ //Widgets
+ QGridLayout *layout;
+ StdCategoryListView *categoryListView;
+ QHBox *buttonBar;
+ QPushButton *newCategoryButton;
+ QPushButton *removeCategoryButton;
+ KIconLoader *il;
+};
+
+#endif
diff --git a/krecipes/src/dialogs/conversiondialog.cpp b/krecipes/src/dialogs/conversiondialog.cpp
new file mode 100644
index 0000000..7746a43
--- /dev/null
+++ b/krecipes/src/dialogs/conversiondialog.cpp
@@ -0,0 +1,166 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "conversiondialog.h"
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <kcombobox.h>
+#include <qlabel.h>
+#include <klineedit.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qvbox.h>
+
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <klocale.h>
+
+#include "backends/recipedb.h"
+#include "widgets/unitcombobox.h"
+#include "widgets/ingredientcombobox.h"
+#include "widgets/prepmethodcombobox.h"
+#include "widgets/fractioninput.h"
+
+ConversionDialog::ConversionDialog( QWidget* parent, RecipeDB *db, const char* name )
+ : KDialogBase( parent, name, false, i18n( "Measurement Converter" ),
+ KDialogBase::Close | KDialogBase::User1 | KDialogBase::Help, KDialogBase::Close ),
+ m_database(db)
+{
+ setHelp("measure-converter");
+ setButtonText( KDialogBase::User1, i18n("Convert") );
+
+ setSizeGripEnabled( TRUE );
+
+ QVBox *page = makeVBoxMainWidget();
+
+ QHBox *vbox = new QVBox(page);
+
+ QHBox *fromTopBox = new QHBox(vbox);
+ convertLabel = new QLabel( fromTopBox, "convertLabel" );
+
+ amountEdit = new FractionInput( fromTopBox );
+
+ fromUnitBox = new UnitComboBox( fromTopBox, db );
+ fromUnitBox->reload();
+ fromTopBox->setStretchFactor( fromUnitBox, 2 );
+ fromTopBox->setSpacing(3);
+
+ QHBox *fromBottomBox = new QHBox(vbox);
+
+ ingredientBox = new IngredientComboBox( FALSE, fromBottomBox, db, i18n( "--Ingredient (optional)--" ) );
+ ingredientBox->reload();
+
+ prepMethodBox = new PrepMethodComboBox( false, fromBottomBox, db, i18n( "-No Preparation-" ) );
+ prepMethodBox->reload();
+ fromBottomBox->setSpacing(3);
+
+ QHBox *toBox = new QHBox(vbox);
+
+ toLabel = new QLabel( toBox, "toLabel" );
+
+ toUnitBox = new UnitComboBox( toBox, db );
+ toUnitBox->reload();
+ toBox->setStretchFactor( toUnitBox, 2 );
+ toBox->setSpacing(8);
+
+ QHBox *resultBox = new QHBox(vbox);
+ resultLabel = new QLabel( resultBox, "resultLabel" );
+ resultText = new QLabel( resultBox, "resultText" );
+ resultBox->setStretchFactor( resultText, 2 );
+
+ languageChange();
+
+ setInitialSize( QSize(300, 200).expandedTo(minimumSizeHint()) );
+
+ // signals and slots connections
+ connect ( this, SIGNAL( closeClicked() ), this, SLOT( accept() ) );
+}
+
+ConversionDialog::~ConversionDialog()
+{
+}
+
+void ConversionDialog::languageChange()
+{
+ convertLabel->setText( i18n( "Convert" ) );
+ toLabel->setText( i18n( "To" ) );
+ resultLabel->setText( i18n( "<b>Result:</b>" ) );
+ resultText->setText( QString::null );
+}
+
+void ConversionDialog::show()
+{
+ reset();
+ KDialogBase::show();
+}
+
+void ConversionDialog::reset()
+{
+ resultText->setText( QString::null );
+ ingredientBox->setCurrentItem( 0 );
+ prepMethodBox->setCurrentItem( 0 );
+ toUnitBox->setCurrentItem( 0 );
+ fromUnitBox->setCurrentItem( 0 );
+ amountEdit->clear();
+}
+
+void ConversionDialog::slotUser1()
+{
+ convert();
+}
+
+void ConversionDialog::convert()
+{
+ Ingredient result, ing;
+ Unit unit = m_database->unitName(toUnitBox->id(toUnitBox->currentItem()));
+
+ ing.amount = amountEdit->value().toDouble();
+ ing.ingredientID = ingredientBox->id(ingredientBox->currentItem());
+ ing.units = m_database->unitName(fromUnitBox->id(fromUnitBox->currentItem()));
+
+ int prepID = prepMethodBox->id(prepMethodBox->currentItem());
+ if ( prepID != -1 )
+ ing.prepMethodList.append(Element(QString::null,prepID));
+
+ switch ( m_database->convertIngredientUnits( ing, unit, result ) ) {
+ case RecipeDB::Success:
+ resultLabel->setText( i18n( "<b>Result:</b>" ) );
+ resultText->setText(QString::number(result.amount)+" "+((result.amount>1)?result.units.plural:result.units.name));
+ break;
+ case RecipeDB::MismatchedPrepMethodUsingApprox:
+ resultLabel->setText( i18n( "<b>Approximated result:</b>" ) );
+ resultText->setText(QString::number(result.amount)+" "+((result.amount>1)?result.units.plural:result.units.name));
+ break;
+ case RecipeDB::MissingUnitConversion:
+ resultLabel->setText( i18n( "<b>Error:</b>" ) );
+ resultText->setText( i18n("Missing unit conversion") );
+ break;
+ case RecipeDB::MissingIngredientWeight:
+ resultLabel->setText( i18n( "<b>Error:</b>" ) );
+ resultText->setText( i18n("No ingredient weight available") );
+ break;
+ case RecipeDB::MismatchedPrepMethod:
+ resultLabel->setText( i18n( "<b>Error:</b>" ) );
+ resultText->setText( i18n("No ingredient weight available for this method of preparation") );
+ break;
+ case RecipeDB::MissingIngredient:
+ resultLabel->setText( i18n( "<b>Error:</b>" ) );
+ resultText->setText( i18n("Ingredient required for conversion") );
+ break;
+ case RecipeDB::InvalidTypes:
+ resultLabel->setText( i18n( "<b>Error:</b>" ) );
+ resultText->setText( i18n("Impossible unit conversion based on unit types") );
+ break;
+ }
+}
+
+#include "conversiondialog.moc"
diff --git a/krecipes/src/dialogs/conversiondialog.h b/krecipes/src/dialogs/conversiondialog.h
new file mode 100644
index 0000000..e48769b
--- /dev/null
+++ b/krecipes/src/dialogs/conversiondialog.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CONVERSIONDIALOG_H
+#define CONVERSIONDIALOG_H
+
+#include <kdialogbase.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class KComboBox;
+class QLabel;
+class KLineEdit;
+class QPushButton;
+
+class RecipeDB;
+class UnitComboBox;
+class IngredientComboBox;
+class PrepMethodComboBox;
+class FractionInput;
+
+class ConversionDialog : public KDialogBase
+{
+Q_OBJECT
+
+public:
+ ConversionDialog( QWidget* parent, RecipeDB *, const char* name = 0 );
+ ~ConversionDialog();
+
+ virtual void show();
+ void reset();
+
+protected:
+ IngredientComboBox* ingredientBox;
+ PrepMethodComboBox* prepMethodBox;
+ QLabel* convertLabel;
+ UnitComboBox* toUnitBox;
+ UnitComboBox* fromUnitBox;
+ FractionInput* amountEdit;
+ QLabel* toLabel;
+ QLabel* resultLabel;
+ QLabel* resultText;
+
+protected slots:
+ virtual void languageChange();
+ void slotUser1();
+ void convert();
+
+private:
+ RecipeDB *m_database;
+};
+
+#endif // CONVERSIONDIALOG_H
diff --git a/krecipes/src/dialogs/createcategorydialog.cpp b/krecipes/src/dialogs/createcategorydialog.cpp
new file mode 100644
index 0000000..fe1cb91
--- /dev/null
+++ b/krecipes/src/dialogs/createcategorydialog.cpp
@@ -0,0 +1,80 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "createcategorydialog.h"
+
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qvbox.h>
+
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <klocale.h>
+
+CreateCategoryDialog::CreateCategoryDialog( QWidget *parent, const ElementList& categories )
+ : KDialogBase( parent, "createCategoryDialog", true, i18n( "New Category" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ box = new QGroupBox( page );
+ box->setColumnLayout( 0, Qt::Vertical );
+ box->layout() ->setSpacing( 6 );
+ box->layout() ->setMargin( 11 );
+ QVBoxLayout *boxLayout = new QVBoxLayout( box->layout() );
+ boxLayout->setAlignment( Qt::AlignTop );
+ box->setTitle( i18n( "New Category" ) );
+
+ elementEdit = new KLineEdit( box );
+ boxLayout->addWidget( elementEdit );
+
+ QHBox *subcatHBox = new QHBox( box );
+ ( void ) new QLabel( i18n( "Subcategory of:" ), subcatHBox );
+ categoryComboBox = new KComboBox( subcatHBox );
+ boxLayout->addWidget( subcatHBox );
+ loadCategories( categories );
+
+ adjustSize();
+ setFixedSize( size() ); //we've got all the widgets put in, now let's keep it this size
+
+ elementEdit->setFocus();
+}
+
+
+CreateCategoryDialog::~CreateCategoryDialog()
+{}
+
+void CreateCategoryDialog::loadCategories( const ElementList& categories )
+{
+ categoryComboBox->insertItem( i18n( "**NONE**" ) );
+ for ( ElementList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ categoryComboBox->insertItem( ( *it ).name );
+ idMap.insert( ( *it ).name, ( *it ).id );
+ }
+}
+
+QString CreateCategoryDialog::newCategoryName( void )
+{
+ return ( elementEdit->text() );
+}
+
+int CreateCategoryDialog::subcategory( void )
+{
+ if ( categoryComboBox->currentItem() == 0 ) {
+ return -1;
+ }
+ else {
+ return idMap[ categoryComboBox->currentText() ];
+ }
+}
diff --git a/krecipes/src/dialogs/createcategorydialog.h b/krecipes/src/dialogs/createcategorydialog.h
new file mode 100644
index 0000000..2bde7b7
--- /dev/null
+++ b/krecipes/src/dialogs/createcategorydialog.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CREATECATEGORYDIALOG_H
+#define CREATECATEGORYDIALOG_H
+
+#include <qmap.h>
+
+#include <kdialogbase.h>
+
+#include "datablocks/elementlist.h"
+
+class KLineEdit;
+class QPushButton;
+class QVBoxLayout;
+class QGroupBox;
+class QVBox;
+class KComboBox;
+
+/**
+@author Jason Kivlighn
+*/
+class CreateCategoryDialog : public KDialogBase
+{
+public:
+ CreateCategoryDialog( QWidget *parent, const ElementList &categories );
+ ~CreateCategoryDialog();
+ QString newCategoryName( void );
+ int subcategory( void );
+
+private:
+ void loadCategories( const ElementList &categories );
+
+ //Widgets
+ QGroupBox *box;
+ KLineEdit *elementEdit;
+ KComboBox* categoryComboBox;
+ QMap<QString, int> idMap;
+
+};
+
+#endif
diff --git a/krecipes/src/dialogs/createelementdialog.cpp b/krecipes/src/dialogs/createelementdialog.cpp
new file mode 100644
index 0000000..11bc973
--- /dev/null
+++ b/krecipes/src/dialogs/createelementdialog.cpp
@@ -0,0 +1,48 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "createelementdialog.h"
+
+#include <klocale.h>
+
+CreateElementDialog::CreateElementDialog( QWidget *parent, const QString &text )
+ : KDialogBase( parent, "createElementDialog", true, text,
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ box = new QGroupBox( page );
+ box->setColumnLayout( 0, Qt::Vertical );
+ box->layout() ->setSpacing( 6 );
+ box->layout() ->setMargin( 11 );
+ QVBoxLayout *boxLayout = new QVBoxLayout( box->layout() );
+ boxLayout->setAlignment( Qt::AlignTop );
+ box->setTitle( text );
+
+ elementEdit = new KLineEdit( box );
+ boxLayout->addWidget( elementEdit );
+
+ adjustSize();
+ setFixedSize( size() ); //we've got all the widgets put in, now let's keep it this size
+
+ elementEdit->setFocus();
+}
+
+
+CreateElementDialog::~CreateElementDialog()
+{}
+
+QString CreateElementDialog::newElementName( void )
+{
+ return ( elementEdit->text() );
+}
+
diff --git a/krecipes/src/dialogs/createelementdialog.h b/krecipes/src/dialogs/createelementdialog.h
new file mode 100644
index 0000000..93406f1
--- /dev/null
+++ b/krecipes/src/dialogs/createelementdialog.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CREATEELEMENTDIALOG_H
+#define CREATEELEMENTDIALOG_H
+
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qvbox.h>
+
+#include <klineedit.h>
+#include <kdialogbase.h>
+
+/**
+@author Unai Garro
+*/
+class CreateElementDialog : public KDialogBase
+{
+public:
+ CreateElementDialog( QWidget *parent, const QString &text );
+ ~CreateElementDialog();
+ QString newElementName( void );
+
+private:
+ //Widgets
+ QGroupBox *box;
+ KLineEdit *elementEdit;
+};
+
+#endif
diff --git a/krecipes/src/dialogs/createingredientweightdialog.cpp b/krecipes/src/dialogs/createingredientweightdialog.cpp
new file mode 100644
index 0000000..1ad3cda
--- /dev/null
+++ b/krecipes/src/dialogs/createingredientweightdialog.cpp
@@ -0,0 +1,124 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "createingredientweightdialog.h"
+
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qvbox.h>
+
+#include <kmessagebox.h>
+#include <klocale.h>
+
+#include "widgets/unitcombobox.h"
+#include "widgets/prepmethodcombobox.h"
+#include "widgets/fractioninput.h"
+#include "datablocks/weight.h"
+#include "backends/recipedb.h"
+
+CreateIngredientWeightDialog::CreateIngredientWeightDialog( QWidget* parent, RecipeDB *db )
+ : KDialogBase( parent, "createIngWeightDialog", true, QString::null,
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ groupBox1 = new QGroupBox( page );
+ groupBox1->setColumnLayout(0, Qt::Vertical );
+ groupBox1->layout()->setSpacing( 6 );
+ groupBox1->layout()->setMargin( 11 );
+ groupBox1Layout = new QGridLayout( groupBox1->layout() );
+ groupBox1Layout->setAlignment( Qt::AlignTop );
+
+ perAmountEdit = new FractionInput( groupBox1 );
+
+ groupBox1Layout->addWidget( perAmountEdit, 1, 1 );
+
+ weightEdit = new FractionInput( groupBox1 );
+
+ groupBox1Layout->addWidget( weightEdit, 0, 1 );
+
+ weightUnitBox = new UnitComboBox( groupBox1, db, Unit::Mass );
+ weightUnitBox->reload();
+
+ groupBox1Layout->addMultiCellWidget( weightUnitBox, 0, 0, 2, 3 );
+
+ perAmountLabel = new QLabel( groupBox1, "perAmountLabel" );
+
+ groupBox1Layout->addWidget( perAmountLabel, 1, 0 );
+
+ weightLabel = new QLabel( groupBox1, "weightLabel" );
+
+ groupBox1Layout->addWidget( weightLabel, 0, 0 );
+
+ perAmountUnitBox = new UnitComboBox( groupBox1, db );
+ perAmountUnitBox->reload();
+
+ groupBox1Layout->addWidget( perAmountUnitBox, 1, 2 );
+
+ prepMethodBox = new PrepMethodComboBox( false, groupBox1, db, i18n("-No Preparation-") );
+ prepMethodBox->reload();
+ groupBox1Layout->addWidget( prepMethodBox, 1, 3 );
+
+ languageChange();
+ clearWState( WState_Polished );
+
+ weightEdit->setFocus();
+}
+
+CreateIngredientWeightDialog::~CreateIngredientWeightDialog()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+void CreateIngredientWeightDialog::languageChange()
+{
+ groupBox1->setTitle( i18n( "New Ingredient Weight" ) );
+ perAmountLabel->setText( i18n( "Per Amount:" ) );
+ weightLabel->setText( i18n( "Weight:" ) );
+}
+
+void CreateIngredientWeightDialog::slotOk()
+{
+ if ( !perAmountEdit->isInputValid() ) {
+ KMessageBox::error( this, i18n( "Amount field contains invalid input." ),
+ i18n( "Invalid input" ) );
+ perAmountEdit->setFocus();
+ perAmountEdit->selectAll();
+ return;
+ }
+ else if ( !weightEdit->isInputValid() ) {
+ KMessageBox::error( this, i18n( "Amount field contains invalid input." ),
+ i18n( "Invalid input" ) );
+ weightEdit->setFocus();
+ weightEdit->selectAll();
+ return;
+ }
+
+ accept();
+}
+
+Weight CreateIngredientWeightDialog::weight() const
+{
+ Weight w;
+ w.perAmount = perAmountEdit->value().toDouble();
+ w.perAmountUnitID = perAmountUnitBox->id( perAmountUnitBox->currentItem() );
+ w.weight = weightEdit->value().toDouble();
+ w.weightUnitID = weightUnitBox->id( weightUnitBox->currentItem() );
+ w.prepMethodID = prepMethodBox->id( prepMethodBox->currentItem() );
+ w.prepMethod = prepMethodBox->currentText();
+
+ return w;
+}
+
+#include "createingredientweightdialog.moc"
diff --git a/krecipes/src/dialogs/createingredientweightdialog.h b/krecipes/src/dialogs/createingredientweightdialog.h
new file mode 100644
index 0000000..bfcd800
--- /dev/null
+++ b/krecipes/src/dialogs/createingredientweightdialog.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CREATEINGREDIENTWEIGHTDIALOG_H
+#define CREATEINGREDIENTWEIGHTDIALOG_H
+
+#include <kdialogbase.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QGroupBox;
+class KLineEdit;
+class KComboBox;
+class QLabel;
+class QPushButton;
+
+class FractionInput;
+class UnitComboBox;
+class PrepMethodComboBox;
+class RecipeDB;
+class Weight;
+
+class CreateIngredientWeightDialog : public KDialogBase
+{
+Q_OBJECT
+
+public:
+ CreateIngredientWeightDialog( QWidget* parent, RecipeDB* );
+ ~CreateIngredientWeightDialog();
+
+ Weight weight() const;
+
+protected:
+ QGridLayout* groupBox1Layout;
+
+protected slots:
+ virtual void languageChange();
+ void slotOk();
+
+private:
+ QGroupBox* groupBox1;
+ FractionInput* perAmountEdit;
+ FractionInput* weightEdit;
+ UnitComboBox* weightUnitBox;
+ QLabel* perAmountLabel;
+ QLabel* weightLabel;
+ UnitComboBox* perAmountUnitBox;
+ PrepMethodComboBox* prepMethodBox;
+};
+
+#endif // CREATEINGREDIENTWEIGHTDIALOG_H
diff --git a/krecipes/src/dialogs/createpropertydialog.cpp b/krecipes/src/dialogs/createpropertydialog.cpp
new file mode 100644
index 0000000..26be442
--- /dev/null
+++ b/krecipes/src/dialogs/createpropertydialog.cpp
@@ -0,0 +1,69 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "createpropertydialog.h"
+
+#include <klocale.h>
+
+CreatePropertyDialog::CreatePropertyDialog( QWidget *parent, UnitList* list )
+ : KDialogBase( parent, "createPropertyDialog", true, i18n( "New Property" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+
+ // Initialize Internal Variables
+ unitList = list; // Store the pointer to the unitList;
+
+ // Initialize widgets
+ QVBox *page = makeVBoxMainWidget();
+
+ box = new QGroupBox( page );
+ box->setColumnLayout( 0, Qt::Vertical );
+ box->layout() ->setSpacing( 6 );
+ box->layout() ->setMargin( 11 );
+ QGridLayout *gridLayout = new QGridLayout( box->layout() );
+ gridLayout->setAlignment( Qt::AlignTop );
+ box->setTitle( i18n( "New Property" ) );
+
+ nameEditText = new QLabel( i18n( "Property name:" ), box );
+ propertyNameEdit = new KLineEdit( box );
+ propertyNameEdit->setMinimumWidth( 150 );
+ gridLayout->addWidget( nameEditText, 0, 0 );
+ gridLayout->addWidget( propertyNameEdit, 0, 1 );
+
+ unitsText = new QLabel( i18n( "Units:" ), box );
+ propertyUnits = new KLineEdit( box );
+ propertyUnits->setMinimumWidth( 150 );
+ gridLayout->addWidget( unitsText, 1, 0 );
+ gridLayout->addWidget( propertyUnits, 1, 1 );
+
+ adjustSize();
+ setFixedSize( size() );
+
+ propertyNameEdit->setFocus();
+}
+
+
+CreatePropertyDialog::~CreatePropertyDialog()
+{}
+
+
+QString CreatePropertyDialog::newPropertyName( void )
+{
+ return ( propertyNameEdit->text() );
+}
+
+QString CreatePropertyDialog::newUnitsName( void )
+{
+ return ( propertyUnits->text() );
+}
+
+
diff --git a/krecipes/src/dialogs/createpropertydialog.h b/krecipes/src/dialogs/createpropertydialog.h
new file mode 100644
index 0000000..4831562
--- /dev/null
+++ b/krecipes/src/dialogs/createpropertydialog.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+/**
+@author Unai Garro
+*/
+
+#ifndef CREATEPROPERTYDIALOG_H
+#define CREATEPROPERTYDIALOG_H
+
+#include <qcombobox.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qvbox.h>
+#include <qlabel.h>
+
+#include <klineedit.h>
+#include <kdialogbase.h>
+
+#include "datablocks/unit.h"
+
+class CreatePropertyDialog : public KDialogBase
+{
+public:
+ CreatePropertyDialog( QWidget *parent, UnitList *list );
+ ~CreatePropertyDialog();
+ QString newPropertyName( void );
+ QString newUnitsName( void );
+
+
+private:
+ //Widgets
+ QGroupBox *box;
+ KLineEdit *propertyNameEdit;
+ KLineEdit *propertyUnits;
+ QLabel *nameEditText;
+ QLabel *unitsText;
+
+ //Internal variables
+ UnitList *unitList;
+
+ //Methods
+ void loadUnits();
+};
+
+#endif
diff --git a/krecipes/src/dialogs/createunitdialog.cpp b/krecipes/src/dialogs/createunitdialog.cpp
new file mode 100644
index 0000000..7f35862
--- /dev/null
+++ b/krecipes/src/dialogs/createunitdialog.cpp
@@ -0,0 +1,116 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "createunitdialog.h"
+
+#include <qlabel.h>
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kcombobox.h>
+
+CreateUnitDialog::CreateUnitDialog( QWidget *parent, const QString &name, const QString &plural, const QString &name_abbrev, const QString &plural_abbrev, bool newUnit )
+ : KDialogBase( parent, "createElementDialog", true, (newUnit)?i18n( "New Unit" ):i18n("Unit"),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ box = new QGroupBox( page );
+ box->setColumnLayout( 0, Qt::Vertical );
+ box->layout() ->setSpacing( 6 );
+ box->layout() ->setMargin( 11 );
+ QGridLayout *gridLayout = new QGridLayout( box->layout() );
+ gridLayout->setAlignment( Qt::AlignTop );
+
+ box->setTitle( (newUnit)?i18n( "New Unit" ):i18n("Unit") );
+
+ QLabel *nameLabel = new QLabel( i18n( "Singular:" ), box );
+ nameEdit = new KLineEdit( name, box );
+
+ gridLayout->addWidget( nameLabel, 0, 0 );
+ gridLayout->addWidget( nameEdit, 0, 1 );
+
+ QLabel *nameAbbrevLabel = new QLabel( i18n( "Abbreviation:" ), box );
+ nameAbbrevEdit = new KLineEdit( name_abbrev, box );
+
+ gridLayout->addWidget( nameAbbrevLabel, 0, 2 );
+ gridLayout->addWidget( nameAbbrevEdit, 0, 3 );
+
+ QLabel *pluralLabel = new QLabel( i18n( "Plural:" ), box );
+ pluralEdit = new KLineEdit( plural, box );
+
+ gridLayout->addWidget( pluralLabel, 1, 0 );
+ gridLayout->addWidget( pluralEdit, 1, 1 );
+
+ QLabel *pluralAbbrevLabel = new QLabel( i18n( "Abbreviation:" ), box );
+ pluralAbbrevEdit = new KLineEdit( plural_abbrev, box );
+
+ gridLayout->addWidget( pluralAbbrevLabel, 1, 2 );
+ gridLayout->addWidget( pluralAbbrevEdit, 1, 3 );
+
+ QLabel *typeLabel = new QLabel( i18n( "Type:" ), box );
+ typeComboBox = new KComboBox( false, box );
+ typeComboBox->insertItem(i18n("Other"));
+ typeComboBox->insertItem(i18n("Mass"));
+ typeComboBox->insertItem(i18n("Volume"));
+
+ gridLayout->addWidget( typeLabel, 2, 0 );
+ gridLayout->addMultiCellWidget( typeComboBox, 2, 2, 1, 3 );
+
+ adjustSize();
+ setFixedSize( size() ); //we've got all the widgets put in, now let's keep it this size
+
+ connect( nameAbbrevEdit, SIGNAL(textChanged(const QString&)), SLOT(nameAbbrevTextChanged(const QString &)) );
+
+ if ( name.isEmpty() )
+ nameEdit->setFocus();
+ else if ( plural.isEmpty() )
+ pluralEdit->setFocus();
+}
+
+
+CreateUnitDialog::~CreateUnitDialog()
+{}
+
+Unit CreateUnitDialog::newUnit( void )
+{
+ QString name = nameEdit->text();
+ QString plural = pluralEdit->text();
+
+ if ( name.isEmpty() )
+ name = plural;
+ if ( plural.isEmpty() )
+ plural = name;
+
+ Unit new_unit = Unit( name, plural );
+ new_unit.name_abbrev = nameAbbrevEdit->text();
+ new_unit.plural_abbrev = pluralAbbrevEdit->text();
+
+ new_unit.type = (Unit::Type)typeComboBox->currentItem();
+
+ return new_unit;
+}
+
+void CreateUnitDialog::nameAbbrevTextChanged(const QString &newText)
+{
+ //appending
+ if ( newText.left( newText.length()-1 ) == pluralAbbrevEdit->text() ) {
+ pluralAbbrevEdit->setText( newText );
+ }
+
+ //truncating
+ if ( newText.left( newText.length()-1 ) == pluralAbbrevEdit->text().left( newText.length()-1 ) ) {
+ pluralAbbrevEdit->setText( newText );
+ }
+}
+
+#include "createunitdialog.moc"
diff --git a/krecipes/src/dialogs/createunitdialog.h b/krecipes/src/dialogs/createunitdialog.h
new file mode 100644
index 0000000..f74fa67
--- /dev/null
+++ b/krecipes/src/dialogs/createunitdialog.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CREATEUNITDIALOG_H
+#define CREATEUNITDIALOG_H
+
+#include <kdialogbase.h>
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qvbox.h>
+
+#include "datablocks/unit.h"
+
+class KComboBox;
+class KLineEdit;
+
+/**
+@author Unai Garro
+*/
+class CreateUnitDialog : public KDialogBase
+{
+Q_OBJECT
+
+public:
+ CreateUnitDialog( QWidget *parent, const QString &name = QString::null, const QString &plural = QString::null, const QString &name_abbrev = QString::null, const QString &plural_abbrev = QString::null, bool newUnit = true );
+ ~CreateUnitDialog();
+ Unit newUnit( void );
+
+protected slots:
+ void nameAbbrevTextChanged(const QString &);
+
+private:
+ //Widgets
+ QGroupBox *box;
+ KLineEdit *nameEdit;
+ KLineEdit *pluralEdit;
+ KLineEdit *nameAbbrevEdit;
+ KLineEdit *pluralAbbrevEdit;
+ KComboBox *typeComboBox;
+};
+
+#endif
diff --git a/krecipes/src/dialogs/dbimportdialog.cpp b/krecipes/src/dialogs/dbimportdialog.cpp
new file mode 100644
index 0000000..6fb946d
--- /dev/null
+++ b/krecipes/src/dialogs/dbimportdialog.cpp
@@ -0,0 +1,220 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "dbimportdialog.h"
+
+#include <unistd.h> //for getuid()
+#include <pwd.h> //getpwuid()
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <qpushbutton.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qwidgetstack.h>
+#include <qwidget.h>
+#include <qlineedit.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qhbox.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kurlrequester.h>
+#include <knuminput.h>
+
+DBImportDialog::DBImportDialog( QWidget *parent, const char *name )
+ : KDialogBase( parent, name, true, i18n( "Database Import" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ setButtonBoxOrientation( Vertical );
+
+ QHBox *page = makeHBoxMainWidget();
+
+ dbButtonGroup = new QButtonGroup( page, "dbButtonGroup" );
+ dbButtonGroup->setSizePolicy( QSizePolicy( ( QSizePolicy::SizeType ) 4, ( QSizePolicy::SizeType ) 5, 0, 0, dbButtonGroup->sizePolicy().hasHeightForWidth() ) );
+ dbButtonGroup->setColumnLayout( 0, Qt::Vertical );
+ dbButtonGroup->layout() ->setSpacing( 6 );
+ dbButtonGroup->layout() ->setMargin( 11 );
+ dbButtonGroupLayout = new QVBoxLayout( dbButtonGroup->layout() );
+ dbButtonGroupLayout->setAlignment( Qt::AlignTop );
+
+ liteRadioButton = new QRadioButton( dbButtonGroup, "liteRadioButton" );
+ liteRadioButton->setChecked( TRUE );
+ dbButtonGroupLayout->addWidget( liteRadioButton );
+
+ mysqlRadioButton = new QRadioButton( dbButtonGroup, "mysqlRadioButton" );
+ dbButtonGroupLayout->addWidget( mysqlRadioButton );
+
+ psqlRadioButton = new QRadioButton( dbButtonGroup, "psqlRadioButton" );
+ dbButtonGroupLayout->addWidget( psqlRadioButton );
+
+ paramStack = new QWidgetStack( page, "paramStack" );
+ paramStack->setSizePolicy( QSizePolicy( ( QSizePolicy::SizeType ) 7, ( QSizePolicy::SizeType ) 5, 0, 0, paramStack->sizePolicy().hasHeightForWidth() ) );
+
+ sqlitePage = new QWidget( paramStack, "sqlitePage" );
+ serverPageLayout_2 = new QVBoxLayout( sqlitePage, 11, 6, "serverPageLayout_2" );
+
+ QLabel *sqliteLabel = new QLabel( i18n( "Database file:" ), sqlitePage );
+ serverPageLayout_2->addWidget( sqliteLabel );
+ sqliteDBRequester = new KURLRequester( sqlitePage, "sqliteDBRequester" );
+ sqliteDBRequester->setShowLocalProtocol( false );
+ serverPageLayout_2->addWidget( sqliteDBRequester );
+
+ QSpacerItem *vSpacer = new QSpacerItem( 20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ serverPageLayout_2->addItem(vSpacer);
+
+ paramStack->addWidget( sqlitePage, 1 );
+
+ serverPage = new QWidget( paramStack, "serverPage" );
+ serverPageLayout = new QVBoxLayout( serverPage, 11, 6, "serverPageLayout" );
+
+ layout5 = new QGridLayout( 0, 1, 1, 0, 6, "layout5" );
+
+ hostEdit = new QLineEdit( serverPage, "hostEdit" );
+ layout5->addWidget( hostEdit, 0, 1 );
+ hostLabel = new QLabel( serverPage, "hostLabel" );
+ layout5->addWidget( hostLabel, 0, 0 );
+
+ userEdit = new QLineEdit( serverPage, "userEdit" );
+ layout5->addWidget( userEdit, 1, 1 );
+ userLabel = new QLabel( serverPage, "userLabel" );
+ layout5->addWidget( userLabel, 1, 0 );
+
+ passwordEdit = new QLineEdit( serverPage, "passwordEdit" );
+ passwordEdit->setEchoMode( QLineEdit::Password );
+ layout5->addWidget( passwordEdit, 2, 1 );
+ passwordLabel = new QLabel( serverPage, "passwordLabel" );
+ layout5->addWidget( passwordLabel, 2, 0 );
+
+ portEdit = new KIntNumInput( serverPage, "portEdit" );
+ portEdit->setMinValue(0);
+ portEdit->setValue(0);
+ layout5->addWidget( portEdit, 3, 1 );
+ portLabel = new QLabel( serverPage, "portLabel" );
+ layout5->addWidget( portLabel, 3, 0 );
+
+ nameEdit = new QLineEdit( serverPage, "nameEdit" );
+ layout5->addWidget( nameEdit, 4, 1 );
+ nameLabel = new QLabel( serverPage, "nameLabel" );
+ layout5->addWidget( nameLabel, 4, 0 );
+
+ serverPageLayout->addLayout( layout5 );
+ paramStack->addWidget( serverPage, 0 );
+
+ languageChange();
+
+
+#if (!HAVE_MYSQL)
+
+ mysqlRadioButton->setEnabled( false );
+#endif
+
+#if (!HAVE_POSTGRESQL)
+
+ psqlRadioButton->setEnabled( false );
+#endif
+
+#if (!(HAVE_SQLITE || HAVE_SQLITE3))
+
+ liteRadioButton->setEnabled( false );
+#if (HAVE_MYSQL)
+
+ dbButtonGroup->setButton( 1 ); // Otherwise by default liteCheckBox is checked even if it's disabled
+ switchDBPage(1);
+#else
+ #if (HAVE_POSTGRESQL)
+
+ dbButtonGroup->setButton( 2 );
+ switchDBPage(2);
+#endif
+ #endif
+ #endif
+
+ // signals and slots connections
+ connect( dbButtonGroup, SIGNAL( clicked( int ) ), this, SLOT( switchDBPage( int ) ) );
+}
+
+void DBImportDialog::languageChange()
+{
+ dbButtonGroup->setTitle( i18n( "Database" ) );
+ liteRadioButton->setText( "SQLite" );
+ mysqlRadioButton->setText( "MySQL" );
+ psqlRadioButton->setText( "PostgreSQL" );
+ hostLabel->setText( i18n( "Server:" ) );
+ userLabel->setText( i18n( "Username:" ) );
+ passwordLabel->setText( i18n( "Password:" ) );
+ nameLabel->setText( i18n( "Database name:" ) );
+ portLabel->setText( i18n( "Port:" ) );
+ portEdit->setSpecialValueText( i18n("Default") );
+
+ //set defaults
+ hostEdit->setText( "localhost" );
+ nameEdit->setText( "Krecipes" );
+
+ // get username
+ uid_t userID;
+ struct passwd *user;
+ userID = getuid();
+ user = getpwuid ( userID );
+ QString username(user->pw_name);
+
+ userEdit->setText( username );
+}
+
+void DBImportDialog::switchDBPage( int id )
+{
+ switch ( id ) {
+ case 0: //SQLite
+ paramStack->raiseWidget( sqlitePage );
+ break;
+ case 1: //MySQL
+ case 2: //PostgreSQL
+ paramStack->raiseWidget( serverPage );
+ break;
+ }
+}
+
+QString DBImportDialog::dbType() const
+{
+ //int id=dbButtonGroup->selectedId(); //QT 3.2
+ int id = dbButtonGroup->id( dbButtonGroup->selected() ); //QT 3.1
+ switch ( id ) {
+ case 0:
+ return "SQLite";
+ case 1:
+ return "MySQL";
+ case 2:
+ return "PostgreSQL";
+ default:
+ return QString::null;
+ }
+}
+
+void DBImportDialog::serverParams( QString &host, QString &user, QString &pass, int &port, QString &table ) const
+{
+ host = hostEdit->text();
+ user = userEdit->text();
+ pass = passwordEdit->text();
+ table = nameEdit->text();
+ port = portEdit->value();
+}
+
+QString DBImportDialog::dbFile() const
+{
+ return sqliteDBRequester->url();
+}
+
+#include "dbimportdialog.moc"
diff --git a/krecipes/src/dialogs/dbimportdialog.h b/krecipes/src/dialogs/dbimportdialog.h
new file mode 100644
index 0000000..37e447a
--- /dev/null
+++ b/krecipes/src/dialogs/dbimportdialog.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef DBIMPORTDIALOG_H
+#define DBIMPORTDIALOG_H
+
+#include <kdialogbase.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QButtonGroup;
+class QRadioButton;
+class QWidgetStack;
+class QWidget;
+class QLineEdit;
+class QLabel;
+class QPushButton;
+
+class KURLRequester;
+class KIntNumInput;
+
+/**
+@author Jason Kivlighn
+*/
+
+class DBImportDialog: public KDialogBase
+{
+ Q_OBJECT
+public:
+ DBImportDialog( QWidget *parent = 0, const char *name = 0 );
+
+ QString dbType() const;
+ void serverParams( QString &host, QString &user, QString &pass, int &port, QString &table ) const;
+ QString dbFile() const;
+
+private:
+ // Widgets
+ QButtonGroup* dbButtonGroup;
+ QRadioButton* liteRadioButton;
+ QRadioButton* mysqlRadioButton;
+ QRadioButton* psqlRadioButton;
+ QWidgetStack* paramStack;
+ QWidget* serverPage;
+ QLineEdit* nameEdit;
+ QLabel* passwordLabel;
+ QLineEdit* hostEdit;
+ QLineEdit* passwordEdit;
+ QLineEdit* userEdit;
+ QLabel* userLabel;
+ QLabel* hostLabel;
+ QLabel* nameLabel;
+ QLabel* portLabel;
+ KIntNumInput *portEdit;
+ QWidget* sqlitePage;
+ KURLRequester* sqliteDBRequester;
+
+ QVBoxLayout* dbButtonGroupLayout;
+ QVBoxLayout* serverPageLayout;
+ QGridLayout* layout5;
+ QVBoxLayout* serverPageLayout_2;
+
+protected slots:
+ void languageChange();
+ void switchDBPage( int id );
+
+};
+
+#endif
diff --git a/krecipes/src/dialogs/dependanciesdialog.cpp b/krecipes/src/dialogs/dependanciesdialog.cpp
new file mode 100644
index 0000000..a352e1e
--- /dev/null
+++ b/krecipes/src/dialogs/dependanciesdialog.cpp
@@ -0,0 +1,100 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "dependanciesdialog.h"
+#include "datablocks/elementlist.h"
+
+#include <qvbox.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+
+DependanciesDialog::DependanciesDialog( QWidget *parent, const QValueList<ListInfo> &lists, bool deps_are_deleted ) : KDialogBase( parent, "DependanciesDialog", true, QString::null,
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Cancel ),
+ m_depsAreDeleted(deps_are_deleted)
+{
+ init( lists );
+}
+
+DependanciesDialog::DependanciesDialog( QWidget *parent, const ListInfo &list, bool deps_are_deleted ) : KDialogBase( parent, "DependanciesDialog", true, QString::null,
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Cancel ),
+ m_depsAreDeleted(deps_are_deleted)
+{
+ QValueList<ListInfo> lists;
+ lists << list;
+ init( lists );
+}
+
+DependanciesDialog::~DependanciesDialog()
+{}
+
+void DependanciesDialog::init( const QValueList<ListInfo> &lists )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ // Design the dialog
+
+ instructionsLabel = new QLabel( page );
+ instructionsLabel->setMinimumSize( QSize( 100, 30 ) );
+ instructionsLabel->setMaximumSize( QSize( 10000, 10000 ) );
+ instructionsLabel->setAlignment( int( QLabel::WordBreak | QLabel::AlignVCenter ) );
+
+ if ( m_depsAreDeleted ) {
+ instructionsLabel->setText( i18n( "<b>WARNING:</b> The following will have to be removed also, since currently they use the element you have chosen to be removed." ) );
+ }
+ else {
+ instructionsLabel->setText( i18n( "<b>WARNING:</b> The following currently use the element you have chosen to be removed." ) );
+ }
+
+ for ( QValueList<ListInfo>::const_iterator list_it = lists.begin(); list_it != lists.end(); ++list_it ) {
+ if ( !((*list_it).list).isEmpty() ) {
+ QGroupBox *groupBox = new QGroupBox( 1, Qt::Vertical, (*list_it).name, page );
+ KListBox *listBox = new KListBox( groupBox );
+ loadList( listBox, (*list_it).list );
+ }
+ }
+
+ setSizeGripEnabled( true );
+}
+
+void DependanciesDialog::loadList( KListBox* listBox, const ElementList &list )
+{
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+
+ for ( ElementList::const_iterator el_it = list.begin(); el_it != list.end(); ++el_it ) {
+ QString name = ( *el_it ).name;
+ if ( show_id )
+ name += " (" + QString::number(( *el_it ).id) + ")";
+ listBox->insertItem( name );
+ }
+}
+
+void DependanciesDialog::accept()
+{
+ if ( !m_msg.isEmpty() ) {
+ switch ( KMessageBox::warningYesNo(this,
+ QString("<b>%1</b><br><br>%2").arg(m_msg).arg(i18n("Are you sure you wish to proceed?")),
+ QString::null,KStdGuiItem::yes(),KStdGuiItem::no(),"doubleCheckDelete") )
+ {
+ case KMessageBox::Yes: QDialog::accept(); break;
+ case KMessageBox::No: QDialog::reject(); break;
+ }
+ }
+ else
+ QDialog::accept();
+}
diff --git a/krecipes/src/dialogs/dependanciesdialog.h b/krecipes/src/dialogs/dependanciesdialog.h
new file mode 100644
index 0000000..07e1696
--- /dev/null
+++ b/krecipes/src/dialogs/dependanciesdialog.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef DEPENDANCIESDIALOG_H
+#define DEPENDANCIESDIALOG_H
+
+#include <qgroupbox.h>
+#include <qlabel.h>
+
+#include <klistview.h>
+#include <kdialogbase.h>
+
+#include "datablocks/elementlist.h"
+
+typedef struct ListInfo {
+ ElementList list;
+ QString name;
+};
+
+/**
+@author Unai Garro
+*/
+class DependanciesDialog: public KDialogBase
+{
+public:
+ //Methods
+ DependanciesDialog( QWidget *parent, const QValueList<ListInfo> &lists, bool deps_are_deleted = true );
+ DependanciesDialog( QWidget *parent, const ListInfo &list, bool deps_are_deleted = true );
+
+ ~DependanciesDialog();
+
+ void setCustomWarning( const QString &msg ){ m_msg = msg; }
+
+public slots:
+ void accept();
+
+private:
+ //Widgets
+ QLabel *instructionsLabel;
+
+ bool m_depsAreDeleted;
+ QString m_msg;
+
+ // Methods
+ void init( const QValueList<ListInfo> &lists );
+ void loadList( KListBox*, const ElementList &list );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/dietviewdialog.cpp b/krecipes/src/dialogs/dietviewdialog.cpp
new file mode 100644
index 0000000..cdb5884
--- /dev/null
+++ b/krecipes/src/dialogs/dietviewdialog.cpp
@@ -0,0 +1,137 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "dietviewdialog.h"
+
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+
+DietViewDialog::DietViewDialog( QWidget *parent, const RecipeList &recipeList, int dayNumber, int mealNumber, const QValueList <int> &dishNumbers )
+ : KDialogBase( parent, "dietViewDialog", true, QString::null,
+ KDialogBase::User2 | KDialogBase::Close | KDialogBase::User1, KDialogBase::User2,
+ false, KStdGuiItem::print() )
+{
+ setButtonText( KDialogBase::User2, i18n( "Create &Shopping List" ) );
+
+ // Design the dialog
+ QVBox *page = makeVBoxMainWidget();
+
+ // The html part
+ dietView = new KHTMLPart( page );
+
+ setInitialSize( QSize(350, 450) );
+
+ setSizeGripEnabled( true );
+
+ connect ( this, SIGNAL( user2Clicked() ), this, SLOT( slotOk() ) );
+ connect ( this, SIGNAL( closeClicked() ), this, SLOT( close() ) );
+ connect ( this, SIGNAL( user1Clicked() ), this, SLOT( print() ) );
+
+ // Show the diet
+ showDiet( recipeList, dayNumber, mealNumber, dishNumbers );
+}
+
+DietViewDialog::~DietViewDialog()
+{}
+
+void DietViewDialog::showDiet( const RecipeList &recipeList, int dayNumber, int mealNumber, const QValueList <int> &dishNumbers )
+{
+
+
+ // Header
+ QString htmlCode = QString( "<html><head><title>%1</title>" ).arg( i18n( "Diet" ) );
+
+ // CSS
+ htmlCode += "<STYLE type=\"text/css\">\n";
+ htmlCode += "#calendar{border: thin solid black}";
+ htmlCode += ".dayheader{ background-color: #D6D6D6; color: black; border:none;}";
+ htmlCode += ".day{ background-color: #E5E5E5; color: black; border:medium solid #D6D6D6;}";
+ htmlCode += ".meal{ background-color: #CDD4FF; color: black; border:thin solid #B4BEFF; text-align:center;}";
+ htmlCode += ".dish{font-size: smaller; overflow: hidden; height:2.5em;}";
+ htmlCode += "</STYLE>";
+
+
+ htmlCode += "</head><body>"; // /Header
+
+ // Calendar border
+ htmlCode += QString( "<div id=\"calendar\">" );
+
+ // Title
+ htmlCode += QString( "<center><div STYLE=\"width: 100%\">" );
+ htmlCode += QString( "<h1>%1</h1></div></center>" ).arg( i18n( "Diet" ) );
+
+ // Diet table
+ htmlCode += QString( "<center><div STYLE=\"width: 98%\">" );
+ htmlCode += QString( "<table><tbody>" );
+
+
+ QValueList <int>::ConstIterator it;
+ it = dishNumbers.begin();
+ RecipeList::ConstIterator rit;
+ rit = recipeList.begin();
+
+ for ( int row = 0, day = 0; row <= ( ( dayNumber - 1 ) / 7 ); row++ ) // New row (week)
+ {
+ htmlCode += QString( "<tr>" );
+
+ for ( int col = 0; ( col < 7 ) && ( day < dayNumber ); col++, day++ ) // New column (day)
+ {
+ htmlCode += QString( "<td><div class=\"day\">" );
+ htmlCode += QString( "<div class=\"dayheader\"><center>" );
+ htmlCode += QString( i18n( "Day %1" ) ).arg( day + 1 );
+ htmlCode += QString( "</center></div>" );
+ for ( int meal = 0;meal < mealNumber;meal++ ) // Meals in each cell
+ {
+ int dishNumber = *it;
+ htmlCode += QString( "<div class=\"meal\">" );
+ for ( int dish = 0; dish < dishNumber;dish++ ) // Dishes in each Meal
+ {
+ htmlCode += QString( "<div class=\"dish\">" );
+ htmlCode += ( *rit ).title;
+ htmlCode += "<br>";
+ htmlCode += QString( "</div>" );
+ rit++;
+ }
+ it++;
+ htmlCode += QString( "</div>" );
+ }
+ it = dishNumbers.begin(); // meals have same dish number everyday
+ htmlCode += QString( "</div></td>" );
+ }
+
+ htmlCode += QString( "</tr>" );
+ }
+
+ htmlCode += QString( "</tbody></table>" );
+ htmlCode += QString( "</div></center>" );
+ htmlCode += QString( "</div></body></html>" );
+
+ resize( QSize( 600, 400 ) );
+
+ // Display it
+ dietView->begin( KURL( locateLocal( "tmp", "/" ) ) ); // Initialize to tmp dir, where photos and logos can be stored
+ dietView->write( htmlCode );
+ dietView->end();
+}
+
+void DietViewDialog::print( void )
+{
+ dietView->view()->print();
+}
+
+void DietViewDialog::slotOk( void )
+{
+ emit signalOk();
+ close();
+}
+
+#include "dietviewdialog.moc"
diff --git a/krecipes/src/dialogs/dietviewdialog.h b/krecipes/src/dialogs/dietviewdialog.h
new file mode 100644
index 0000000..a388316
--- /dev/null
+++ b/krecipes/src/dialogs/dietviewdialog.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef DIETVIEWDIALOG_H
+#define DIETVIEWDIALOG_H
+
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+#include <khtml_part.h>
+#include <khtmlview.h>
+#include <kdialogbase.h>
+
+#include "datablocks/recipelist.h"
+
+class DietViewDialog: public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ DietViewDialog( QWidget *parent, const RecipeList &recipeList, int dayNumber, int mealNumber, const QValueList <int> &dishNumbers );
+ ~DietViewDialog();
+private:
+ // Widgets
+ KHTMLPart *dietView;
+
+ // Private methods
+ void showDiet( const RecipeList &recipeList, int dayNumber, int mealNumber, const QValueList <int> &dishNumbers );
+private slots:
+ void print( void );
+ void slotOk( void );
+signals:
+ void signalOk( void );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/dietwizarddialog.cpp b/krecipes/src/dialogs/dietwizarddialog.cpp
new file mode 100644
index 0000000..50a4435
--- /dev/null
+++ b/krecipes/src/dialogs/dietwizarddialog.cpp
@@ -0,0 +1,799 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "dietwizarddialog.h"
+#include "backends/recipedb.h"
+#include "dietviewdialog.h"
+
+#include <qbitmap.h>
+#include <qheader.h>
+#include <qlayout.h>
+#include <qpainter.h>
+#include <qwmatrix.h>
+
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <knuminput.h>
+#include <kdebug.h>
+
+#include "propertycalculator.h"
+#include "widgets/propertylistview.h"
+#include "widgets/categorylistview.h"
+
+#include "profiling.h"
+
+DietWizardDialog::DietWizardDialog( QWidget *parent, RecipeDB *db ) : QVBox( parent )
+{
+ // Initialize internal variables
+ database = db;
+ mealNumber = 1;
+ dayNumber = 1;
+ dietRList = new RecipeList();
+
+ //Design the dialog
+ setSpacing( 5 );
+ // Options Box
+ optionsBox = new QHBox( this );
+
+ daysSliderBox = new QVGroupBox( i18n( "Number of Days" ), optionsBox );
+ dayNumberLabel = new QLabel( daysSliderBox );
+ dayNumberLabel->setText( "- 1 -" );
+ dayNumberLabel->setAlignment( Qt::AlignHCenter );
+ dayNumberSelector = new QSlider( daysSliderBox );
+
+ dayNumberSelector->setOrientation( Qt::Horizontal );
+ dayNumberSelector->setRange( 1, 10 );
+ dayNumberSelector->setSteps( 1, 1 );
+ dayNumberSelector->setTickmarks( QSlider::Below );
+ dayNumberSelector->setFixedWidth( 100 );
+
+ mealsSliderBox = new QVGroupBox( i18n( "Meals per Day" ), optionsBox );
+ mealNumberLabel = new QLabel( mealsSliderBox );
+ mealNumberLabel->setText( "- 1 -" );
+ mealNumberLabel->setAlignment( Qt::AlignHCenter );
+ mealNumberSelector = new QSlider( mealsSliderBox );
+
+ mealNumberSelector->setOrientation( Qt::Horizontal );
+ mealNumberSelector->setRange( 1, 10 );
+ mealNumberSelector->setSteps( 1, 1 );
+ mealNumberSelector->setTickmarks( QSlider::Below );
+ mealNumberSelector->setFixedWidth( 100 );
+
+ // Tabs
+ mealTabs = new QTabWidget( this );
+ mealTabs->setMargin( 5 );
+
+ // Button bar
+ KIconLoader il;
+
+ QHBox *bottom_layout = new QHBox( this );
+ //bottom_layout->layout()->addItem( new QSpacerItem( 10,10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+
+ okButton = new QPushButton( bottom_layout );
+ okButton->setIconSet( il.loadIconSet( "button_ok", KIcon::Small ) );
+ okButton->setText( i18n( "Create the diet" ) );
+
+ QPushButton *clearButton = new QPushButton( bottom_layout );
+ clearButton->setIconSet( il.loadIconSet( "editclear", KIcon::Small ) );
+ clearButton->setText( i18n( "Clear" ) );
+
+ // Create Tabs
+ //don't use newTab, it'll load data and we don't want it to do that at startup
+ mealTab = new MealInput( mealTabs, database );
+ mealTabs->addTab( mealTab,i18n( "Meal 1" ) );
+ mealTabs->setCurrentPage( mealTabs->indexOf( mealTab ) );
+
+ // Signals & Slots
+ connect( mealNumberSelector, SIGNAL( valueChanged( int ) ), this, SLOT( changeMealNumber( int ) ) );
+ connect( dayNumberSelector, SIGNAL( valueChanged( int ) ), this, SLOT( changeDayNumber( int ) ) );
+ connect( okButton, SIGNAL( clicked() ), this, SLOT( createDiet() ) );
+ connect( clearButton, SIGNAL( clicked() ), this, SLOT( clear() ) );
+}
+
+
+DietWizardDialog::~DietWizardDialog()
+{
+ delete dietRList;
+}
+
+void DietWizardDialog::clear()
+{
+ mealNumberSelector->setValue( 1 );
+ dayNumberSelector->setValue( 1 );
+
+ MealInput* mealTab = ( MealInput* ) ( mealTabs->page( 0 ) ); // Get the meal
+ mealTab->setDishNo( 3 );
+ mealTab->showDish( 0 );
+
+ for ( uint i = 0; i < mealTab->dishInputList.count(); ++i ) {
+ DishInput* dishInput = mealTab->dishInputList[ i ]; // Get the dish input
+ dishInput->clear();
+ }
+}
+
+void DietWizardDialog::reload( ReloadFlags flag )
+{
+ for ( int i = 0; i < mealTabs->count(); ++i ) {
+ MealInput *mealTab = (MealInput*)mealTabs->page(i);
+ mealTab->reload(flag);
+ }
+}
+
+void DietWizardDialog::newTab( const QString &name )
+{
+ mealTab = new MealInput( mealTabs, database );
+ mealTab->reload();
+ mealTabs->addTab( mealTab, name );
+ mealTabs->setCurrentPage( mealTabs->indexOf( mealTab ) );
+}
+
+void DietWizardDialog::changeMealNumber( int mn )
+{
+ mealNumberLabel->setText( QString( i18n( "- %1 -" ) ).arg( mn ) );
+ if ( mn > mealNumber ) {
+
+ while ( mealNumber != mn ) {
+ mealNumber++;
+ newTab( i18n( "Meal %1" ).arg( mealNumber ) );
+
+ }
+ }
+ else if ( mn < mealNumber ) {
+
+ while ( mealNumber != mn ) {
+ mealNumber--;
+ delete mealTabs->page( mealTabs->count() - 1 );
+ }
+ }
+}
+
+void DietWizardDialog::changeDayNumber( int dn )
+{
+
+ if ( dn < 7 ) {
+ dayNumber = dn;
+ dayNumberLabel->setText( QString( i18n( "- %1 -" ) ).arg( dn ) );
+ }
+ else if ( dn == 7 ) {
+ dayNumber = 7;
+ dayNumberLabel->setText( QString( i18n( "- 1 week -" ) ) );
+ }
+ else {
+ dayNumber = ( dn - 6 ) * 7;
+ dayNumberLabel->setText( QString( i18n( "- %1 weeks -" ) ).arg( dn - 6 ) );
+ }
+}
+
+void DietWizardDialog::createDiet( void )
+{
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ START_TIMER("Creating the diet");
+
+ RecipeList rlist;
+ dietRList->clear();
+
+ // Get the whole list of recipes, detailed
+ int flags = RecipeDB::Title | getNecessaryFlags();
+ database->loadRecipes( &rlist, flags );
+
+ // temporal iterator list so elements can be removed without reloading them again from the DB
+ // this list prevents the same meal from showing up in the same day twice
+ QValueList <RecipeList::Iterator> tempRList;
+
+ bool alert = false;
+
+ for ( int day = 0;day < dayNumber;day++ ) // Create the diet for the number of days defined by the user
+ {
+ populateIteratorList( rlist, &tempRList ); // temporal iterator list so elements can be removed without reloading them again from the DB
+ for ( int meal = 0;meal < mealNumber;meal++ )
+ {
+ int dishNo = ( ( MealInput* ) ( mealTabs->page( meal ) ) ) ->dishNo();
+
+ for ( int dish = 0;dish < dishNo;dish++ ) {
+ bool found = false;
+ QValueList <RecipeList::Iterator> tempDishRList = tempRList;
+ while ( ( !found ) && !tempDishRList.empty() ) {
+ int random_index = ( int ) ( ( float ) ( kapp->random() ) / ( float ) RAND_MAX * tempDishRList.count() );
+ QValueList<RecipeList::Iterator>::Iterator iit = tempDishRList.at( random_index ); // note that at() retrieves an iterator to the iterator list, so we need to use * in order to get the RecipeList::Iterator
+
+ RecipeList::Iterator rit = *iit;
+ if ( found = ( ( ( !categoryFiltering( meal, dish ) ) || checkCategories( *rit, meal, dish ) ) && checkConstraints( *rit, meal, dish ) ) ) // Check that the recipe is inside the constraint limits and in the categories specified
+ {
+ dietRList->append( *rit ); // Add recipe to the diet list
+ tempRList.remove( tempRList.find(*iit) ); //can't just remove()... the iterator isn't from this list (its an iterator from tempDishRList)
+ }
+ else {
+ tempDishRList.remove( iit ); // Remove this analized recipe from teh list
+ }
+ }
+ if ( !found )
+ alert = true;
+ }
+ }
+ }
+
+ if ( alert ) {
+ KApplication::restoreOverrideCursor();
+ KMessageBox::sorry( this, i18n( "I could not create a full diet list given the constraints. Either the recipe list is too short or the constraints are too demanding. " ) );
+ }
+
+ else // show the resulting diet
+ {
+
+ // make a list of dishnumbers
+ QValueList<int> dishNumbers;
+
+ for ( int meal = 0;meal < mealNumber;meal++ ) {
+ int dishNo = ( ( MealInput* ) ( mealTabs->page( meal ) ) ) ->dishNo();
+ dishNumbers << dishNo;
+ }
+
+ KApplication::restoreOverrideCursor();
+
+ // display the list
+ DietViewDialog dietDisplay( this, *dietRList, dayNumber, mealNumber, dishNumbers );
+ connect( &dietDisplay, SIGNAL( signalOk() ), this, SLOT( createShoppingList() ) );
+ dietDisplay.exec();
+ }
+
+ END_TIMER();
+}
+
+
+void DietWizardDialog::populateIteratorList( RecipeList &rl, QValueList <RecipeList::Iterator> *il )
+{
+ il->clear();
+ RecipeList::Iterator it;
+ for ( it = rl.begin();it != rl.end(); ++it )
+ il->append( it );
+
+}
+
+int DietWizardDialog::getNecessaryFlags() const
+{
+ bool need_ingredients = false;
+ bool need_categories = false;
+ for ( int meal = 0;meal < mealNumber;meal++ ) {
+ int dishNo = ( ( MealInput* ) ( mealTabs->page( meal ) ) ) ->dishNo();
+ for ( int dish = 0;dish < dishNo;dish++ ) {
+ if ( !need_categories ) {
+ if ( categoryFiltering( meal, dish ) ) {
+ need_categories = true;
+ }
+ }
+
+ if ( !need_ingredients ) {
+ ConstraintList constraints;
+ loadConstraints( meal, dish, &constraints );
+ for ( ConstraintList::const_iterator ct_it = constraints.begin(); ct_it != constraints.end(); ++ct_it ) {
+ if ( (*ct_it).enabled ) {
+ need_ingredients = true;
+ break;
+ }
+ }
+ }
+
+ if ( need_ingredients && need_categories )
+ break;
+ }
+
+ if ( need_ingredients && need_categories )
+ break;
+ }
+
+ kdDebug()<<"Do we need to load ingredients: "<<need_ingredients<<endl;
+ kdDebug()<<"Do we need to load categories: "<<need_categories<<endl;
+
+ int flags = 0;
+ if ( need_ingredients ) flags |= RecipeDB::Ingredients;
+ if ( need_categories ) flags |= RecipeDB::Categories;
+
+ return flags;
+}
+
+
+MealInput::MealInput( QWidget *parent, RecipeDB *db ) : QWidget( parent ),
+ database( db )
+{
+ // Design the dialog
+ QVBoxLayout *layout = new QVBoxLayout( this );
+ layout->setSpacing( 10 );
+
+ // Options box
+
+ mealOptions = new QHBox( this );
+ mealOptions->setSpacing( 10 );
+ layout->addWidget( mealOptions );
+
+ // Number of dishes input
+ dishNumberBox = new QHBox( mealOptions );
+ dishNumberBox->setSpacing( 10 );
+ dishNumberLabel = new QLabel( i18n( "No. of dishes: " ), dishNumberBox );
+ dishNumberInput = new QSpinBox( dishNumberBox );
+ dishNumberInput->setMinValue( 1 );
+ dishNumberInput->setMaxValue( 10 );
+ dishNumberBox->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) );
+
+ // Toolbar
+
+ toolBar = new QHGroupBox( mealOptions );
+ toolBar->setFrameStyle ( QFrame::Panel | QFrame::Sunken );
+ toolBar->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum ) );
+
+ // Next dish/ Previous dish buttons
+ KIconLoader il;
+ buttonPrev = new QToolButton( toolBar );
+ buttonPrev->setUsesTextLabel( true );
+ buttonPrev->setTextLabel( i18n( "Previous Dish" ) );
+ buttonPrev->setIconSet( il.loadIconSet( "back", KIcon::Small ) );
+ buttonPrev->setTextPosition( QToolButton::Under );
+ buttonNext = new QToolButton( toolBar );
+ buttonNext->setUsesTextLabel( true );
+ buttonNext->setTextLabel( i18n( "Next Dish" ) );
+ buttonNext->setIconSet( il.loadIconSet( "forward", KIcon::Small ) );
+ buttonNext->setTextPosition( QToolButton::Under );
+
+
+ // Dish widgets
+ dishStack = new QWidgetStack( this );
+ layout->addWidget( dishStack );
+ dishStack->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+ // Add default dishes
+ DishInput *newDish = new DishInput( this, database, i18n( "1st Course" ) );
+ dishStack->addWidget( newDish );
+ dishInputList.append( newDish );
+ newDish = new DishInput( this, database, i18n( "2nd Course" ) );
+ dishStack->addWidget( newDish );
+ dishInputList.append( newDish );
+ newDish = new DishInput( this, database, i18n( "Dessert" ) );
+ dishStack->addWidget( newDish );
+ dishInputList.append( newDish );
+ dishNumber = 3;
+ dishNumberInput->setValue( dishNumber );
+
+ // Signals & Slots
+ connect( dishNumberInput, SIGNAL( valueChanged( int ) ), this, SLOT( changeDishNumber( int ) ) );
+ connect( buttonPrev, SIGNAL( clicked() ), this, SLOT( prevDish() ) );
+ connect( buttonNext, SIGNAL( clicked() ), this, SLOT( nextDish() ) );
+
+}
+
+MealInput::~MealInput()
+{}
+
+void MealInput::reload( ReloadFlags flag )
+{
+ QValueList<DishInput*>::iterator it;
+ for ( it = dishInputList.begin(); it != dishInputList.end(); ++it ) {
+ DishInput *di = ( *it );
+ di->reload(flag);
+ }
+}
+
+void MealInput::setDishNo( int dn )
+{
+ dishNumberInput->setValue( dn );
+}
+
+void MealInput::changeDishNumber( int dn )
+{
+ if ( dn > dishNumber ) {
+ while ( dishNumber != dn ) {
+ DishInput * newDish = new DishInput( this, database, QString( i18n( "Dish %1" ) ).arg( dishNumber + 1 ) );
+ newDish->reload();
+ dishStack->addWidget( newDish );
+ dishInputList.append( newDish );
+ dishStack->raiseWidget( newDish );
+ dishNumber++;
+ }
+ }
+ else if ( dn < dishNumber ) {
+ QValueList <DishInput*>::Iterator it;
+ while ( dishNumber != dn ) {
+ it = dishInputList.fromLast();
+ DishInput *lastDish = ( *it );
+ dishInputList.remove( it );
+
+ if ( ( *it ) == ( DishInput* ) dishStack->visibleWidget() ) {
+ it--;
+ dishStack->raiseWidget( *it );
+ }
+ delete lastDish;
+ dishNumber--;
+ }
+ }
+}
+
+
+void MealInput::nextDish( void )
+{
+ // First get the place of the current dish input in the list
+ QValueList <DishInput*>::iterator it = dishInputList.find( ( DishInput* ) ( dishStack->visibleWidget() ) );
+
+ //Show the next dish if it exists
+ it++;
+ if ( it != dishInputList.end() ) {
+ dishStack->raiseWidget( *it );
+ }
+
+}
+
+void MealInput::prevDish( void )
+{
+ // First get the place of the current dish input in the list
+ QValueList <DishInput*>::iterator it = dishInputList.find( ( DishInput* ) ( dishStack->visibleWidget() ) );
+
+ //Show the previous dish if it exists
+ it--;
+ if ( it != dishInputList.end() ) {
+ dishStack->raiseWidget( *it );
+ }
+}
+
+void MealInput::showDish( int dn )
+{
+ QValueList <DishInput*>::iterator it = dishInputList.at( dn );
+ if ( it != dishInputList.end() )
+ dishStack->raiseWidget( *it );
+}
+
+DishInput::DishInput( QWidget* parent, RecipeDB *db, const QString &title ) : QWidget( parent ),
+ database(db)
+{
+
+ // Initialize internal variables
+ categoryFiltering = false;
+
+ // Design the widget
+
+ QVBoxLayout *layout = new QVBoxLayout( this );
+ layout->setSpacing( 10 );
+
+ //Horizontal Box to hold the KListView's
+ listBox = new QHGroupBox( i18n( "Dish Characteristics" ), this );
+ layout->addWidget( listBox );
+
+ // Dish id
+ dishTitle = new DishTitle( listBox, title );
+
+ //Categories list
+ categoriesBox = new QVBox( listBox );
+ categoriesEnabledBox = new QCheckBox( categoriesBox );
+ categoriesEnabledBox->setText( i18n( "Enable Category Filtering" ) );
+
+ categoriesView = new CategoryCheckListView( categoriesBox, database, false );
+ categoriesView->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored );
+ categoriesView->setEnabled( false ); // Disable it by default
+
+ //Constraints list
+ constraintsView = new PropertyConstraintListView( listBox, database );
+ constraintsView->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored );
+ constraintsView->reload();
+
+ // KDoubleInput based edit boxes
+ constraintsEditBox1 = new KDoubleNumInput( constraintsView->viewport() );
+ constraintsView->addChild( constraintsEditBox1 );
+ constraintsEditBox1->hide();
+ constraintsEditBox2 = new KDoubleNumInput( constraintsView->viewport() );
+ constraintsView->addChild( constraintsEditBox2 );
+ constraintsEditBox2->hide();
+
+
+ // Connect Signals & Slots
+ connect( constraintsView, SIGNAL( executed( QListViewItem* ) ), this, SLOT( insertConstraintsEditBoxes( QListViewItem* ) ) );
+ connect( constraintsView, SIGNAL( selectionChanged() ), this, SLOT( hideConstraintInputs() ) );
+ connect( constraintsEditBox1, SIGNAL( valueChanged( double ) ), this, SLOT( setMinValue( double ) ) );
+ connect( constraintsEditBox2, SIGNAL( valueChanged( double ) ), this, SLOT( setMaxValue( double ) ) );
+ connect( categoriesEnabledBox, SIGNAL( toggled( bool ) ), this, SLOT( enableCategories( bool ) ) );
+}
+
+DishInput::~DishInput()
+{}
+
+void DishInput::clear()
+{
+ QListViewItemIterator it( categoriesView );
+ while ( it.current() ) {
+ QCheckListItem * item = ( QCheckListItem* ) it.current();
+ item->setOn( false );
+ ++it;
+ }
+
+ constraintsView->reload();
+ categoriesEnabledBox->setChecked( false );
+}
+
+void DishInput::enableCategories( bool enable )
+{
+ categoriesView->setEnabled( enable );
+ categoryFiltering = enable;
+}
+
+bool DishInput::isCategoryFilteringEnabled( void ) const
+{
+ return categoryFiltering;
+}
+
+void DishInput::reload( ReloadFlags flag )
+{
+ constraintsView->reload();
+ categoriesView->reload(flag);
+}
+
+void DishInput::insertConstraintsEditBoxes( QListViewItem* it )
+{
+ QRect r;
+
+ // Constraints Box1
+ r = constraintsView->header() ->sectionRect( 2 ); //start at the section 2 header
+ r.moveBy( 0, constraintsView->itemRect( it ).y() ); //Move down to the item, note that its height is same as header's right now.
+
+ r.setHeight( it->height() ); // Set the item's height
+ r.setWidth( constraintsView->header() ->sectionRect( 2 ).width() ); // and width
+ constraintsEditBox1->setGeometry( r );
+
+
+ //Constraints Box2
+ r = constraintsView->header() ->sectionRect( 3 ); //start at the section 3 header
+ r.moveBy( 0, constraintsView->itemRect( it ).y() ); //Move down to the item
+
+ r.setHeight( it->height() ); // Set the item's height
+ r.setWidth( constraintsView->header() ->sectionRect( 3 ).width() ); // and width
+ constraintsEditBox2->setGeometry( r );
+
+ // Set the values from the item
+ constraintsEditBox1->setValue( ( ( ConstraintsListItem* ) it ) ->minVal() );
+ constraintsEditBox2->setValue( ( ( ConstraintsListItem* ) it ) ->maxVal() );
+
+ // Show Boxes
+ constraintsEditBox1->show();
+ constraintsEditBox2->show();
+}
+
+void DishInput::hideConstraintInputs()
+{
+ constraintsEditBox1->hide();
+ constraintsEditBox2->hide();
+}
+
+void DishInput::loadConstraints( ConstraintList *constraints ) const
+{
+ constraints->clear();
+ Constraint constraint;
+ for ( ConstraintsListItem * it = ( ConstraintsListItem* ) ( constraintsView->firstChild() );it;it = ( ConstraintsListItem* ) ( it->nextSibling() ) ) {
+ constraint.id = it->propertyId();
+ constraint.min = it->minVal();
+ constraint.max = it->maxVal();
+ constraint.enabled = it->isOn();
+ constraints->append( constraint );
+ }
+}
+
+void DishInput::loadEnabledCategories( ElementList* categories )
+{
+ categories->clear();
+
+ // Only load those that are checked, unless filtering is disabled
+ if ( !categoriesView->isEnabled() ) {
+ database->loadCategories(categories);
+ }
+ else {
+ *categories = categoriesView->selections();
+ }
+}
+
+void DishInput::setMinValue( double minValue )
+{
+ ConstraintsListItem *it = ( ConstraintsListItem* ) ( constraintsView->selectedItem() ); // Find selected property
+
+ if ( it )
+ it->setMin( minValue );
+}
+
+void DishInput::setMaxValue( double maxValue )
+{
+ ConstraintsListItem *it = ( ConstraintsListItem* ) ( constraintsView->selectedItem() ); // Find selected property
+
+ if ( it )
+ it->setMax( maxValue );
+}
+
+DishTitle::DishTitle( QWidget *parent, const QString &title ) : QWidget( parent )
+{
+ titleText = title;
+}
+
+
+DishTitle::~DishTitle()
+{}
+
+void DishTitle::paintEvent( QPaintEvent * )
+{
+
+
+
+ // Now draw the text
+
+ if ( QT_VERSION >= 0x030200 ) {
+ // Case 1: Qt 3.2+
+
+ QPainter painter( this );
+
+ // First draw the decoration
+ painter.setPen( KGlobalSettings::activeTitleColor() );
+ painter.setBrush( QBrush( KGlobalSettings::activeTitleColor() ) );
+ painter.drawRoundRect( 0, 20, 30, height() - 40, 50, ( int ) ( 50.0 / ( height() - 40 ) * 35.0 ) );
+
+ // Now draw the text
+
+ QFont titleFont = KGlobalSettings::windowTitleFont ();
+ titleFont.setPointSize( 15 );
+ painter.setFont( titleFont );
+ painter.rotate( -90 );
+ painter.setPen( QColor( 0x00, 0x00, 0x00 ) );
+ painter.drawText( 0, 0, -height(), 30, AlignCenter, titleText );
+ painter.setPen( QColor( 0xFF, 0xFF, 0xFF ) );
+ painter.drawText( -1, -1, -height() - 1, 29, AlignCenter, titleText );
+ painter.end();
+ }
+ else {
+ // Case 2: Qt 3.1
+
+ // Use a pixmap
+
+ QSize pmSize( height(), width() ); //inverted size so we can rotate later
+ QPixmap pm( pmSize );
+ pm.fill( QColor( 0xFF, 0xFF, 0xFF ) );
+ QPainter painter( &pm );
+
+ // First draw the decoration
+ painter.setPen( KGlobalSettings::activeTitleColor() );
+ painter.setBrush( QBrush( KGlobalSettings::activeTitleColor() ) );
+ painter.drawRoundRect( 20, 0, height() - 40, 30, ( int ) ( 50.0 / ( height() - 40 ) * 35.0 ), 50 );
+
+ // Now draw the text
+ QFont titleFont = KGlobalSettings::windowTitleFont ();
+ titleFont.setPointSize( 15 );
+ painter.setFont( titleFont );
+ painter.setPen( QColor( 0x00, 0x00, 0x00 ) );
+ painter.drawText( 0, 0, height(), 30, AlignCenter, titleText );
+ painter.setPen( QColor( 0xFF, 0xFF, 0xFF ) );
+ painter.drawText( -1, -1, height() - 1, 29, AlignCenter, titleText );
+ painter.end();
+
+ //Set the border transparent using a mask
+ QBitmap mask( pm.size() );
+ mask.fill( Qt::color0 );
+ painter.begin( &mask );
+ painter.setPen( Qt::color1 );
+ painter.setBrush( Qt::color1 );
+ painter.drawRoundRect( 20, 0, height() - 40, 30, ( int ) ( 50.0 / ( height() - 40 ) * 35.0 ), 50 );
+
+ painter.end();
+ pm.setMask( mask );
+
+ //And Rotate
+ QWMatrix m ;
+ m.rotate( -90 );
+ pm = pm.xForm( m );
+
+ bitBlt( this, 0, 0, &pm );
+ }
+
+}
+QSize DishTitle::sizeHint () const
+{
+ return ( QSize( 40, 200 ) );
+}
+
+QSize DishTitle::minimumSizeHint() const
+{
+ return ( QSize( 40, 200 ) );
+}
+
+bool DietWizardDialog::checkCategories( Recipe &rec, int meal, int dish )
+{
+
+ // Check if the recipe is among the categories chosen
+ ElementList categoryList;
+ loadEnabledCategories( meal, dish, &categoryList );
+
+
+ for ( ElementList::const_iterator cat_it = rec.categoryList.begin(); cat_it != rec.categoryList.end(); ++cat_it ) {
+ if ( categoryList.containsId( ( *cat_it ).id ) )
+ return true;
+ }
+
+ return false;
+}
+
+/*
+** Calculate the recipe Properties and check if they're within the constraints
+*/
+
+bool DietWizardDialog::checkConstraints( Recipe &rec, int meal, int dish )
+{
+ // Check if the properties are within the constraints
+ ConstraintList constraints;
+ loadConstraints( meal, dish, &constraints ); //load the constraints
+
+ bool any_enabled = false;
+ for ( ConstraintList::const_iterator ct_it = constraints.begin(); ct_it != constraints.end(); ++ct_it ) {
+ if ( (*ct_it).enabled ) {
+ any_enabled = true;
+ break;
+ }
+ }
+ if ( !any_enabled )
+ return true;
+
+ // Calculate properties of the recipe
+ calculateProperties( rec, database );
+
+ bool withinLimits = checkLimits( rec.properties, constraints );
+
+ return ( withinLimits );
+}
+
+void DietWizardDialog::loadConstraints( int meal, int dish, ConstraintList *constraints ) const
+{
+ MealInput * mealTab = ( MealInput* ) ( mealTabs->page( meal ) ); // Get the meal
+ DishInput* dishInput = mealTab->dishInputList[ dish ]; // Get the dish input
+ dishInput->loadConstraints( constraints ); //Load the constraints form the KListView
+}
+
+void DietWizardDialog::loadEnabledCategories( int meal, int dish, ElementList *categories )
+{
+ MealInput * mealTab = ( MealInput* ) ( mealTabs->page( meal ) ); // Get the meal
+ DishInput* dishInput = mealTab->dishInputList[ dish ]; // Get the dish input
+ dishInput->loadEnabledCategories( categories ); //Load the categories that have been checked in the KListView
+}
+
+bool DietWizardDialog::categoryFiltering( int meal, int dish ) const
+{
+ MealInput * mealTab = ( MealInput* ) ( mealTabs->page( meal ) ); // Get the meal
+ DishInput* dishInput = mealTab->dishInputList[ dish ]; // Get the dish input
+ return ( dishInput->isCategoryFilteringEnabled() ); //Load the categories that have been checked in the KListView
+}
+
+bool DietWizardDialog::checkLimits( IngredientPropertyList &properties, ConstraintList &constraints )
+{
+ for ( ConstraintList::const_iterator ct_it = constraints.begin(); ct_it != constraints.end(); ++ct_it ) {
+ if ( (*ct_it).enabled ) {
+ IngredientPropertyList::const_iterator ip_it = properties.find( (*ct_it).id );
+ if ( ip_it != properties.end() ) {
+ if ( ( (*ip_it).amount > (*ct_it).max ) || ( (*ip_it).amount < (*ct_it).min ) )
+ return false;
+ }
+ else
+ return false;
+ }
+ }
+ return true;
+}
+
+void DietWizardDialog::createShoppingList( void )
+{
+ emit dietReady();
+}
+
+RecipeList& DietWizardDialog::dietList( void )
+{
+ return *dietRList;
+}
+
+#include "dietwizarddialog.moc"
diff --git a/krecipes/src/dialogs/dietwizarddialog.h b/krecipes/src/dialogs/dietwizarddialog.h
new file mode 100644
index 0000000..05570a7
--- /dev/null
+++ b/krecipes/src/dialogs/dietwizarddialog.h
@@ -0,0 +1,222 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef DIETWIZARDDIALOG_H
+#define DIETWIZARDDIALOG_H
+
+#include <stdlib.h> // For RAND_MAX
+
+#include <qcheckbox.h>
+#include <qhbox.h>
+#include <qhgroupbox.h>
+#include <qlabel.h>
+#include <qptrlist.h>
+#include <qpushbutton.h>
+#include <qslider.h>
+#include <qspinbox.h>
+#include <qtabwidget.h>
+#include <qtoolbutton.h>
+#include <qvaluelist.h>
+#include <qvbox.h>
+#include <qvgroupbox.h>
+#include <qwidgetstack.h>
+
+#include <klistview.h>
+
+#include "datablocks/constraintlist.h"
+#include "datablocks/recipelist.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/recipe.h"
+#include "datablocks/unitratiolist.h"
+
+#include "widgets/dblistviewbase.h"
+
+class KDoubleNumInput;
+
+class DishInput;
+class DishTitle;
+class MealInput;
+class RecipeDB;
+class RecipeList;
+class CategoryCheckListView;
+class PropertyConstraintListView;
+
+/**
+@author Unai Garro
+*/
+
+
+class DietWizardDialog: public QVBox
+{
+
+ Q_OBJECT
+
+public:
+
+ DietWizardDialog( QWidget *parent, RecipeDB* db );
+ ~DietWizardDialog();
+
+private:
+ //Private variables
+ RecipeDB *database;
+
+ int dayNumber;
+ int mealNumber;
+
+ RecipeList *dietRList;
+
+ //Widgets
+ QHBox *optionsBox;
+ QVGroupBox *mealsSliderBox;
+ QLabel *mealNumberLabel;
+ QSlider *mealNumberSelector;
+ QVGroupBox *daysSliderBox;
+ QLabel *dayNumberLabel;
+ QSlider *dayNumberSelector;
+ QTabWidget *mealTabs;
+ MealInput *mealTab; // points to the current tab
+ QPushButton *okButton;
+
+ //Methods
+ bool checkCategories( Recipe &rec, int meal, int dish );
+ bool checkConstraints( Recipe &rec, int meal, int dish );
+ bool checkLimits( IngredientPropertyList &properties, ConstraintList &constraints );
+ void loadConstraints( int meal, int dish, ConstraintList *constraints ) const;
+ void loadEnabledCategories( int meal, int dish, ElementList *categories );
+ void newTab( const QString &name );
+ bool categoryFiltering( int meal, int dish ) const;
+ int getNecessaryFlags() const;
+
+public:
+ //Methods
+ void reload( ReloadFlags flags = Load );
+ RecipeList& dietList( void );
+
+private slots:
+ void changeDayNumber( int dn );
+ void changeMealNumber( int mn );
+ void createDiet( void );
+ void clear();
+ void createShoppingList( void );
+ void populateIteratorList( RecipeList &rl, QValueList <RecipeList::Iterator> *il );
+signals:
+ void dietReady( void );
+};
+
+class MealInput: public QWidget
+{
+ Q_OBJECT
+
+public:
+ // Methods
+
+ MealInput( QWidget *parent, RecipeDB *database );
+ ~MealInput();
+ void reload( ReloadFlags flag = Load );
+ int dishNo( void )
+ {
+ return dishNumber;
+ };
+ void setDishNo( int dn );
+ void showDish( int dn );
+
+ // Public widgets and variables
+ QValueList <DishInput*> dishInputList; // The list of dishes
+
+private:
+ // Widgets
+ // Private Variables
+ int dishNumber;
+ RecipeDB *database;
+
+ // Settings section for the meal
+ QHBox *mealOptions;
+
+ // Dish number setting
+ QHBox *dishNumberBox;
+ QLabel *dishNumberLabel;
+ QSpinBox *dishNumberInput;
+
+ // Move <-> buttons
+ QHGroupBox *toolBar;
+ QToolButton *buttonNext;
+ QToolButton *buttonPrev;
+
+ // Settings for the dish
+ QWidgetStack *dishStack;
+
+public slots:
+ void nextDish( void );
+ void prevDish( void );
+
+private slots:
+ void changeDishNumber( int dn );
+
+};
+
+class DishInput: public QWidget
+{
+ Q_OBJECT
+
+public:
+ DishInput( QWidget *parent, RecipeDB *database, const QString &title );
+ ~DishInput();
+ // Methods
+ bool isCategoryFilteringEnabled( void ) const;
+ void loadConstraints( ConstraintList *constraints ) const;
+ void loadEnabledCategories( ElementList* categories );
+ void reload( ReloadFlags flag = Load );
+ void setDishTitle( const QString & text );
+ void clear();
+
+
+private:
+ // Variables
+ bool categoryFiltering;
+ // Widgets
+ QHGroupBox *listBox;
+ DishTitle *dishTitle;
+ QVBox *categoriesBox;
+ QCheckBox *categoriesEnabledBox;
+ CategoryCheckListView *categoriesView;
+ PropertyConstraintListView *constraintsView;
+ KDoubleNumInput *constraintsEditBox1;
+ KDoubleNumInput *constraintsEditBox2;
+ RecipeDB *database;
+
+private slots:
+ void enableCategories( bool enable );
+ void insertConstraintsEditBoxes( QListViewItem* it );
+ void hideConstraintInputs();
+ void setMinValue( double minValue );
+ void setMaxValue( double maxValue );
+};
+
+class DishTitle: public QWidget
+{
+
+ Q_OBJECT
+
+public:
+ DishTitle( QWidget *parent, const QString &title );
+ ~DishTitle();
+ virtual QSize sizeHint () const;
+ virtual QSize minimumSizeHint() const;
+protected:
+ //Variables
+ QString titleText;
+ //Methods
+ virtual void paintEvent( QPaintEvent *p );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/editratingdialog.cpp b/krecipes/src/dialogs/editratingdialog.cpp
new file mode 100644
index 0000000..505240f
--- /dev/null
+++ b/krecipes/src/dialogs/editratingdialog.cpp
@@ -0,0 +1,243 @@
+/***************************************************************************
+* Copyright (C) 2005 by Jason Kivlighn *
+* jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "editratingdialog.h"
+
+#include <klocale.h>
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <qcombobox.h>
+#include <knuminput.h>
+#include <qheader.h>
+#include <klistview.h>
+#include <qtextedit.h>
+#include <qlineedit.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qpainter.h>
+#include <qvbox.h>
+
+#include <kpopupmenu.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+
+#include "datablocks/rating.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/mixednumber.h"
+
+#include "widgets/ratingwidget.h"
+
+class RatingCriteriaListView : public KListView
+{
+public:
+ RatingCriteriaListView( QWidget *parent = 0, const char *name = 0 ) : KListView(parent,name){}
+
+ void rename( QListViewItem *it, int c )
+ {
+ if ( c == 1 )
+ it->setPixmap(c,QPixmap());
+
+ KListView::rename(it,c);
+ }
+};
+
+
+EditRatingDialog::EditRatingDialog( const ElementList &criteriaList, const Rating &rating, QWidget* parent, const char* name )
+ : KDialogBase( parent, name, true, i18n( "Rating" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ init(criteriaList);
+ loadRating(rating);
+}
+
+/*
+ * Constructs a EditRatingDialog as a child of 'parent', with the
+ * name 'name' and widget flags set to 'f'.
+ */
+EditRatingDialog::EditRatingDialog( const ElementList &criteriaList, QWidget* parent, const char* name )
+ : KDialogBase( parent, name, true, i18n( "Rating" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok )
+{
+ init(criteriaList);
+}
+
+void EditRatingDialog::init( const ElementList &criteriaList )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ layout2 = new QHBox( page );
+
+ raterLabel = new QLabel( layout2, "raterLabel" );
+ raterEdit = new QLineEdit( layout2, "raterEdit" );
+
+ layout8 = new QHBox( page );
+
+ criteriaLabel = new QLabel( layout8, "criteriaLabel" );
+
+ criteriaComboBox = new QComboBox( FALSE, layout8, "criteriaComboBox" );
+ criteriaComboBox->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, (QSizePolicy::SizeType)0, 0, 0, criteriaComboBox->sizePolicy().hasHeightForWidth() ) );
+ criteriaComboBox->setEditable( TRUE );
+ criteriaComboBox->lineEdit()->disconnect( criteriaComboBox ); //so hitting enter doesn't enter the item into the box
+
+ starsLabel = new QLabel( layout8, "starsLabel" );
+
+ starsWidget = new RatingWidget( 5, layout8, "starsWidget" );
+
+ addButton = new QPushButton( layout8, "addButton" );
+
+ removeButton = new QPushButton( layout8, "removeButton" );
+
+ criteriaListView = new RatingCriteriaListView( page, "criteriaListView" );
+ criteriaListView->addColumn( i18n( "Criteria" ) );
+ criteriaListView->addColumn( i18n( "Stars" ) );
+ criteriaListView->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)7, (QSizePolicy::SizeType)7, 0, 0, criteriaListView->sizePolicy().hasHeightForWidth() ) );
+ criteriaListView->setSorting(-1);
+ criteriaListView->setItemsRenameable( true );
+ criteriaListView->setRenameable( 0, true );
+ criteriaListView->setRenameable( 1, true );
+
+ commentsLabel = new QLabel( page, "commentsLabel" );
+
+ commentsEdit = new QTextEdit( page, "commentsEdit" );
+
+ languageChange();
+ resize( QSize(358, 331).expandedTo(minimumSizeHint()) );
+ clearWState( WState_Polished );
+
+ connect( criteriaListView, SIGNAL(itemRenamed(QListViewItem*,const QString &,int)), this, SLOT(itemRenamed(QListViewItem*,const QString &,int)) );
+ connect( addButton, SIGNAL(clicked()), this, SLOT(slotAddRatingCriteria()) );
+ connect( removeButton, SIGNAL(clicked()), this, SLOT(slotRemoveRatingCriteria()) );
+
+ KIconLoader il;
+ KPopupMenu *kpop = new KPopupMenu( criteriaListView );
+ kpop->insertItem( il.loadIcon( "editshred", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( slotRemoveRatingCriteria() ), Key_Delete );
+
+ for ( ElementList::const_iterator criteria_it = criteriaList.begin(); criteria_it != criteriaList.end(); ++criteria_it ) {
+ criteriaComboBox->insertItem( ( *criteria_it ).name );
+ //criteriaComboBox->completionObject()->addItem( ( *criteria_it ).name );
+ }
+
+ ratingID = -1;
+}
+
+/*
+ * Destroys the object and frees any allocated resources
+ */
+EditRatingDialog::~EditRatingDialog()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+/*
+ * Sets the strings of the subwidgets using the current
+ * language.
+ */
+void EditRatingDialog::languageChange()
+{
+ criteriaLabel->setText( i18n( "Criteria:" ) );
+ starsLabel->setText( i18n( "Stars:" ) );
+ addButton->setText( i18n( "Add" ) );
+ removeButton->setText( i18n( "&Delete" ) );
+ criteriaListView->header()->setLabel( 0, i18n( "Criteria" ) );
+ criteriaListView->header()->setLabel( 1, i18n( "Stars" ) );
+ commentsLabel->setText( i18n( "Comments:" ) );
+ raterLabel->setText( i18n( "Rater:" ) );
+}
+
+void EditRatingDialog::itemRenamed(QListViewItem* it, const QString &, int c)
+{
+ if ( c == 1 ) {
+ bool ok = false;
+ MixedNumber stars_mn = MixedNumber::fromString(it->text(c),&ok);
+ if ( ok && !it->text(c).isEmpty() ) {
+ double stars = QMAX(0,QMIN(stars_mn.toDouble(),5)); //force to between 0 and 5
+ QPixmap starsPic = Rating::starsPixmap( stars );
+ it->setPixmap(c,starsPic);
+ it->setText(2,QString::number(stars));
+ }
+ else {
+ double stars = it->text(2).toDouble(); //col 2 holds the old value, which we'll set it back to
+ QPixmap starsPic = Rating::starsPixmap( stars );
+ it->setPixmap(c,starsPic);
+ }
+
+ it->setText(c,QString::null);
+ }
+}
+
+Rating EditRatingDialog::rating() const
+{
+ Rating r;
+
+ for ( QListViewItem *it = criteriaListView->firstChild(); it; it = it->nextSibling() ) {
+ RatingCriteria rc;
+ rc.name = it->text(0);
+ rc.stars = it->text(2).toDouble();
+ r.append( rc );
+ }
+
+ r.comment = commentsEdit->text();
+ r.rater = raterEdit->text();
+
+ r.id = ratingID;
+
+ return r;
+}
+
+void EditRatingDialog::loadRating( const Rating &rating )
+{
+ for ( RatingCriteriaList::const_iterator rc_it = rating.ratingCriteriaList.begin(); rc_it != rating.ratingCriteriaList.end(); ++rc_it ) {
+ addRatingCriteria(*rc_it);
+ }
+
+ raterEdit->setText(rating.rater);
+ commentsEdit->setText(rating.comment);
+
+ ratingID = rating.id;
+}
+
+void EditRatingDialog::slotAddRatingCriteria()
+{
+ RatingCriteria r;
+ r.name = criteriaComboBox->lineEdit()->text().stripWhiteSpace();
+ if ( r.name.isEmpty() )
+ return;
+
+ r.stars = starsWidget->text().toDouble();
+
+ addRatingCriteria(r);
+
+ criteriaComboBox->lineEdit()->clear();
+ starsWidget->clear();
+
+ criteriaComboBox->lineEdit()->setFocus();
+}
+
+void EditRatingDialog::addRatingCriteria( const RatingCriteria &rc )
+{
+ QListViewItem * it = new QListViewItem(criteriaListView,rc.name);
+
+ QPixmap stars = Rating::starsPixmap(rc.stars);
+ if ( !stars.isNull() ) //there aren't zero stars
+ it->setPixmap(1,stars);
+
+ it->setText(2,QString::number(rc.stars));
+}
+
+void EditRatingDialog::slotRemoveRatingCriteria()
+{
+ delete criteriaListView->selectedItem();
+}
+
+#include "editratingdialog.moc"
diff --git a/krecipes/src/dialogs/editratingdialog.h b/krecipes/src/dialogs/editratingdialog.h
new file mode 100644
index 0000000..bf981c8
--- /dev/null
+++ b/krecipes/src/dialogs/editratingdialog.h
@@ -0,0 +1,77 @@
+/***************************************************************************
+* Copyright (C) 2005 by Jason Kivlighn *
+* jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef EDITRATINGDIALOG_H
+#define EDITRATINGDIALOG_H
+
+#include <kdialogbase.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QLabel;
+class QComboBox;
+class KDoubleSpinBox;
+class QPushButton;
+class KListView;
+class QListViewItem;
+class QTextEdit;
+class QLineEdit;
+class RatingWidget;
+
+class Rating;
+class RatingCriteria;
+class ElementList;
+class RatingCriteriaListView;
+
+class EditRatingDialog : public KDialogBase
+{
+Q_OBJECT
+
+public:
+ EditRatingDialog( const ElementList &criteriaList, const Rating &, QWidget* parent = 0, const char* name = 0 );
+ EditRatingDialog( const ElementList &criteriaList, QWidget* parent = 0, const char* name = 0 );
+ ~EditRatingDialog();
+
+ QLabel* criteriaLabel;
+ QComboBox* criteriaComboBox;
+ QLabel* starsLabel;
+ RatingWidget *starsWidget;
+ QPushButton* addButton;
+ QPushButton* removeButton;
+ RatingCriteriaListView* criteriaListView;
+ QLabel* commentsLabel;
+ QTextEdit* commentsEdit;
+ QLabel* raterLabel;
+ QLineEdit* raterEdit;
+
+ Rating rating() const;
+
+protected:
+ QHBox* layout8;
+ QHBox* layout2;
+
+protected slots:
+ virtual void languageChange();
+ void slotAddRatingCriteria();
+ void slotRemoveRatingCriteria();
+ void itemRenamed(QListViewItem* it, const QString &, int c);
+
+private:
+ void init(const ElementList &criteriaList);
+
+ void loadRating( const Rating & );
+ void addRatingCriteria( const RatingCriteria &rc );
+
+ int ratingID;
+};
+
+#endif // EDITRATINGDIALOG_H
diff --git a/krecipes/src/dialogs/ingredientgroupsdialog.cpp b/krecipes/src/dialogs/ingredientgroupsdialog.cpp
new file mode 100644
index 0000000..9110fcc
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientgroupsdialog.cpp
@@ -0,0 +1,65 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientgroupsdialog.h"
+
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qvbox.h>
+
+#include <klocale.h>
+#include <kdialog.h>
+
+#include "widgets/krelistview.h"
+#include "widgets/headerlistview.h"
+
+IngredientGroupsDialog::IngredientGroupsDialog( RecipeDB *db, QWidget *parent, const char *name ) : QWidget(parent,name), database(db)
+{
+ QHBoxLayout* layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ headerListView = new KreListView ( this, i18n( "Header list" ), true, 0 );
+ StdHeaderListView *list_view = new StdHeaderListView( headerListView, database, true );
+ headerListView->setListView( list_view );
+ headerListView->setSizePolicy( QSizePolicy( QSizePolicy::Ignored, QSizePolicy::MinimumExpanding ) );
+ layout->addWidget(headerListView);
+
+ QVBoxLayout *buttonLayout = new QVBoxLayout(this);
+ QPushButton *addHeaderButton = new QPushButton( this );
+ addHeaderButton->setText( "+" );
+ addHeaderButton->setMinimumSize( QSize( 30, 30 ) );
+ addHeaderButton->setMaximumSize( QSize( 30, 30 ) );
+ addHeaderButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ addHeaderButton->setFlat( true );
+ buttonLayout->addWidget(addHeaderButton);
+
+ QSpacerItem* spacer_buttons = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Fixed );
+ buttonLayout->addItem( spacer_buttons );
+
+ QPushButton *removeHeaderButton = new QPushButton( this );
+ removeHeaderButton->setText( "-" );
+ removeHeaderButton->setMinimumSize( QSize( 30, 30 ) );
+ removeHeaderButton->setMaximumSize( QSize( 30, 30 ) );
+ removeHeaderButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ removeHeaderButton->setFlat( true );
+ buttonLayout->addWidget(removeHeaderButton);
+
+ QSpacerItem* spacer_below_buttons = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
+ buttonLayout->addItem( spacer_below_buttons );
+
+ layout->addLayout(buttonLayout);
+
+ connect( addHeaderButton, SIGNAL( clicked() ), list_view, SLOT( createNew() ) );
+ connect( removeHeaderButton, SIGNAL( clicked() ), list_view, SLOT( remove() ) );
+}
+
+void IngredientGroupsDialog::reload( ReloadFlags flag )
+{
+ ( ( StdHeaderListView* ) headerListView->listView() ) ->reload(flag);
+}
diff --git a/krecipes/src/dialogs/ingredientgroupsdialog.h b/krecipes/src/dialogs/ingredientgroupsdialog.h
new file mode 100644
index 0000000..3a2dec8
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientgroupsdialog.h
@@ -0,0 +1,33 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTGROUPSDIALOG_H
+#define INGREDIENTGROUPSDIALOG_H
+
+#include <qwidget.h>
+
+#include "widgets/dblistviewbase.h"
+
+class KreListView;
+class RecipeDB;
+
+class IngredientGroupsDialog : public QWidget
+{
+public:
+ IngredientGroupsDialog( RecipeDB *db, QWidget *parent, const char *name );
+
+ void reload( ReloadFlags flag = Load );
+
+private:
+ KreListView *headerListView;
+ RecipeDB *database;
+};
+
+#endif
diff --git a/krecipes/src/dialogs/ingredientmatcherdialog.cpp b/krecipes/src/dialogs/ingredientmatcherdialog.cpp
new file mode 100644
index 0000000..fb5d174
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientmatcherdialog.cpp
@@ -0,0 +1,353 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientmatcherdialog.h"
+
+#include "datablocks/recipelist.h"
+#include "widgets/ingredientlistview.h"
+#include "datablocks/elementlist.h"
+#include "backends/recipedb.h"
+#include "widgets/krelistview.h"
+#include "widgets/unitcombobox.h"
+#include "widgets/fractioninput.h"
+#include "widgets/amountunitinput.h"
+#include "datablocks/mixednumber.h"
+#include "recipeactionshandler.h"
+
+#include <qheader.h>
+#include <qpainter.h>
+#include <qstringlist.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <knuminput.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kdebug.h>
+#include <kdialogbase.h>
+
+#include "profiling.h"
+
+IngredientMatcherDialog::IngredientMatcherDialog( QWidget *parent, RecipeDB *db ) : QWidget( parent )
+{
+ // Initialize internal variables
+ database = db;
+
+ //Design the dialog
+
+ QVBoxLayout *dialogLayout = new QVBoxLayout( this, 11, 6 );
+
+ // Ingredient list
+ QHBoxLayout *layout2 = new QHBoxLayout( 0, 0, 6, "layout2" );
+
+ allIngListView = new KreListView( this, QString::null, true, 0 );
+ StdIngredientListView *list_view = new StdIngredientListView(allIngListView,database);
+ list_view->setSelectionMode( QListView::Multi );
+ allIngListView->setListView(list_view);
+ layout2->addWidget( allIngListView );
+
+ QVBoxLayout *layout1 = new QVBoxLayout( 0, 0, 6, "layout1" );
+
+ KIconLoader il;
+
+ addButton = new QPushButton( this, "addButton" );
+ addButton->setIconSet( il.loadIconSet( "forward", KIcon::Small ) );
+ addButton->setFixedSize( QSize( 32, 32 ) );
+ layout1->addWidget( addButton );
+
+ removeButton = new QPushButton( this, "removeButton" );
+ removeButton->setIconSet( il.loadIconSet( "back", KIcon::Small ) );
+ removeButton->setFixedSize( QSize( 32, 32 ) );
+ layout1->addWidget( removeButton );
+ QSpacerItem *spacer1 = new QSpacerItem( 51, 191, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ layout1->addItem( spacer1 );
+ layout2->addLayout( layout1 );
+
+ ingListView = new KreListView( this, QString::null, true );
+ ingListView->listView() ->addColumn( i18n( "Ingredient (required?)" ) );
+ ingListView->listView() ->addColumn( i18n( "Amount Available" ) );
+ layout2->addWidget( ingListView );
+ dialogLayout->addLayout( layout2 );
+
+ // Box to select allowed number of missing ingredients
+ missingBox = new QHBox( this );
+ missingNumberLabel = new QLabel( missingBox );
+ missingNumberLabel->setText( i18n( "Missing ingredients allowed:" ) );
+ missingNumberSpinBox = new KIntSpinBox( missingBox );
+ missingNumberSpinBox->setMinValue( -1 );
+ missingNumberSpinBox->setSpecialValueText( i18n( "Any" ) );
+ dialogLayout->addWidget(missingBox);
+
+ // Found recipe list
+ recipeListView = new KreListView( this, i18n( "Matching Recipes" ), false, 1, missingBox );
+ recipeListView->listView() ->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored );
+ recipeListView->listView() ->setAllColumnsShowFocus( true );
+
+ recipeListView->listView() ->addColumn( i18n( "Title" ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ recipeListView->listView() ->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ recipeListView->listView() ->addColumn( i18n( "Missing Ingredients" ) );
+
+ recipeListView->listView() ->setSorting( -1 );
+ dialogLayout->addWidget(recipeListView);
+
+ RecipeActionsHandler *actionHandler = new RecipeActionsHandler( recipeListView->listView(), database, RecipeActionsHandler::Open | RecipeActionsHandler::Edit | RecipeActionsHandler::Export );
+
+ QHBox *buttonBox = new QHBox( this );
+
+ okButton = new QPushButton( buttonBox );
+ okButton->setIconSet( il.loadIconSet( "button_ok", KIcon::Small ) );
+ okButton->setText( i18n( "Find matching recipes" ) );
+
+ //buttonBox->layout()->addItem( new QSpacerItem( 10,10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+
+ clearButton = new QPushButton( buttonBox );
+ clearButton->setIconSet( il.loadIconSet( "editclear", KIcon::Small ) );
+ clearButton->setText( i18n( "Clear" ) );
+ dialogLayout->addWidget(buttonBox);
+
+ // Connect signals & slots
+ connect ( okButton, SIGNAL( clicked() ), this, SLOT( findRecipes() ) );
+ connect ( clearButton, SIGNAL( clicked() ), recipeListView->listView(), SLOT( clear() ) );
+ connect ( clearButton, SIGNAL( clicked() ), this, SLOT( unselectIngredients() ) );
+ connect ( actionHandler, SIGNAL( recipeSelected( int, int ) ), SIGNAL( recipeSelected( int, int ) ) );
+ connect( addButton, SIGNAL( clicked() ), this, SLOT( addIngredient() ) );
+ connect( removeButton, SIGNAL( clicked() ), this, SLOT( removeIngredient() ) );
+ connect( ingListView->listView(), SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), SLOT( itemRenamed( QListViewItem*, const QPoint &, int ) ) );
+}
+
+IngredientMatcherDialog::~IngredientMatcherDialog()
+{
+}
+
+void IngredientMatcherDialog::itemRenamed( QListViewItem* item, const QPoint &, int col )
+{
+ if ( col == 1 ) {
+ KDialogBase amountEditDialog(this,"IngredientMatcherAmountEdit",
+ false, i18n("Enter amount"), KDialogBase::Cancel | KDialogBase::Ok, KDialogBase::Ok);
+
+ QGroupBox *box = new QGroupBox( 1, Horizontal, i18n("Amount"), &amountEditDialog );
+ AmountUnitInput *amountEdit = new AmountUnitInput( box, database );
+ // Set the values from the item
+ if ( !item->text(1).isEmpty() ) {
+ amountEdit->setAmount( MixedNumber::fromString(item->text(2)) );
+ Unit u;
+ u.id = item->text(3).toInt();
+ amountEdit->setUnit( u );
+ } else {
+ amountEdit->setAmount( -1 );
+ Unit u;
+ u.id = -1;
+ amountEdit->setUnit( u );
+ }
+
+ amountEditDialog.setMainWidget(box);
+
+ if ( amountEditDialog.exec() == QDialog::Accepted ) {
+ MixedNumber amount = amountEdit->amount();
+ Unit unit = amountEdit->unit();
+
+ if ( amount.toDouble() <= 1e-5 ) {
+ amount = -1;
+ unit.id = -1;
+
+ item->setText(1,QString::null);
+ } else {
+ item->setText(1,amount.toString()+" "+((amount.toDouble()>1)?unit.plural:unit.name));
+ }
+
+ item->setText(2,amount.toString());
+ item->setText(3,QString::number(unit.id));
+
+ IngredientList::iterator ing_it = m_item_ing_map[item];
+ (*ing_it).amount = amount.toDouble();
+ (*ing_it).units = unit;
+ }
+ }
+}
+
+void IngredientMatcherDialog::addIngredient()
+{
+ QPtrList<QListViewItem> items = allIngListView->listView()->selectedItems();
+ if ( items.count() > 0 ) {
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ bool dup = false;
+ for ( QListViewItem *exists_it = ingListView->listView()->firstChild(); exists_it; exists_it = exists_it->nextSibling() ) {
+ if ( exists_it->text( 0 ) == item->text( 0 ) ) {
+ dup = true;
+ break;
+ }
+ }
+
+ if ( !dup ) {
+ QListViewItem * new_item = new QCheckListItem( ingListView->listView(), item->text( 0 ), QCheckListItem::CheckBox );
+
+ ingListView->listView() ->setSelected( new_item, true );
+ ingListView->listView() ->ensureItemVisible( new_item );
+ allIngListView->listView() ->setSelected( item, false );
+
+ m_item_ing_map.insert( new_item, m_ingredientList.append( Ingredient( item->text( 0 ), 0, Unit(), -1, item->text( 1 ).toInt() ) ) );
+ }
+ ++it;
+ }
+ }
+}
+
+void IngredientMatcherDialog::removeIngredient()
+{
+ QListViewItem * item = ingListView->listView() ->selectedItem();
+ if ( item ) {
+ for ( IngredientList::iterator it = m_ingredientList.begin(); it != m_ingredientList.end(); ++it ) {
+ if ( *m_item_ing_map.find( item ) == it ) {
+ m_ingredientList.remove( it );
+ m_item_ing_map.remove( item );
+ break;
+ }
+ }
+ delete item;
+ }
+}
+
+void IngredientMatcherDialog::unselectIngredients()
+{
+ ingListView->listView()->clear();
+ for ( QListViewItem *it = allIngListView->listView()->firstChild(); it; it = it->nextSibling() )
+ allIngListView->listView()->setSelected(it,false);
+}
+
+void IngredientMatcherDialog::findRecipes( void )
+{
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ START_TIMER("Ingredient Matcher: loading database data");
+
+ RecipeList rlist;
+ database->loadRecipes( &rlist, RecipeDB::Title | RecipeDB::NamesOnly | RecipeDB::Ingredients | RecipeDB::IngredientAmounts );
+
+ END_TIMER();
+ START_TIMER("Ingredient Matcher: analyzing data for matching recipes");
+
+ // Clear the list
+ recipeListView->listView() ->clear();
+
+ // Now show the recipes with ingredients that are contained in the previous set
+ // of ingredients
+ RecipeList incompleteRecipes;
+ QValueList <int> missingNumbers;
+ QValueList <IngredientList> missingIngredients;
+
+ RecipeList::Iterator it;
+ for ( it = rlist.begin();it != rlist.end();++it ) {
+ IngredientList il = ( *it ).ingList;
+ if ( il.isEmpty() )
+ continue;
+
+ IngredientList missing;
+ if ( m_ingredientList.containsSubSet( il, missing, true, database ) ) {
+ new CustomRecipeListItem( recipeListView->listView(), *it );
+ }
+ else {
+ incompleteRecipes.append( *it );
+ missingIngredients.append( missing );
+ missingNumbers.append( missing.count() );
+ }
+ }
+ END_TIMER();
+
+ //Check if the user wants to show missing ingredients
+
+ if ( missingNumberSpinBox->value() == 0 ) {
+ KApplication::restoreOverrideCursor();
+ return ;
+ } //"None"
+
+
+
+ START_TIMER("Ingredient Matcher: searching for and displaying partial matches");
+
+ IngredientList requiredIngredients;
+ for ( QListViewItem *it = ingListView->listView()->firstChild(); it; it = it->nextSibling() ) {
+ if ( ((QCheckListItem*)it)->isOn() )
+ requiredIngredients << *m_item_ing_map[it];
+ }
+
+ // Classify recipes with missing ingredients in different lists by ammount
+ QValueList<int>::Iterator nit;
+ QValueList<IngredientList>::Iterator ilit;
+ int missingNoAllowed = missingNumberSpinBox->value();
+
+ if ( missingNoAllowed == -1 ) // "Any"
+ {
+ for ( nit = missingNumbers.begin();nit != missingNumbers.end();++nit )
+ if ( ( *nit ) > missingNoAllowed )
+ missingNoAllowed = ( *nit );
+ }
+
+
+ for ( int missingNo = 1; missingNo <= missingNoAllowed; missingNo++ ) {
+ nit = missingNumbers.begin();
+ ilit = missingIngredients.begin();
+
+ bool titleShownYet = false;
+
+ for ( it = incompleteRecipes.begin();it != incompleteRecipes.end();++it, ++nit, ++ilit ) {
+ if ( !( *it ).ingList.containsAny( m_ingredientList ) )
+ continue;
+
+ if ( !( *it ).ingList.containsSubSet( requiredIngredients ) )
+ continue;
+
+ if ( ( *nit ) == missingNo ) {
+ if ( !titleShownYet ) {
+ new SectionItem( recipeListView->listView(), i18n( "You are missing 1 ingredient for:", "You are missing %n ingredients for:", missingNo ) );
+ titleShownYet = true;
+ }
+ new CustomRecipeListItem( recipeListView->listView(), *it, *ilit );
+ }
+ }
+ }
+ END_TIMER();
+
+ KApplication::restoreOverrideCursor();
+}
+
+void IngredientMatcherDialog::reload( ReloadFlags flag )
+{
+ ( ( StdIngredientListView* ) allIngListView->listView() ) ->reload(flag);
+}
+
+void SectionItem::paintCell ( QPainter * p, const QColorGroup & /*cg*/, int column, int width, int /*align*/ )
+{
+ // Draw the section's deco
+ p->setPen( KGlobalSettings::activeTitleColor() );
+ p->setBrush( KGlobalSettings::activeTitleColor() );
+ p->drawRect( 0, 0, width, height() );
+
+ // Draw the section's text
+ if ( column == 0 ) {
+ QFont titleFont = KGlobalSettings::windowTitleFont ();
+ p->setFont( titleFont );
+
+ p->setPen( KGlobalSettings::activeTextColor() );
+ p->drawText( 0, 0, width, height(), Qt::AlignLeft | Qt::AlignVCenter, mText );
+ }
+}
+
+#include "ingredientmatcherdialog.moc"
diff --git a/krecipes/src/dialogs/ingredientmatcherdialog.h b/krecipes/src/dialogs/ingredientmatcherdialog.h
new file mode 100644
index 0000000..eacd80c
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientmatcherdialog.h
@@ -0,0 +1,156 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTMATCHERDIALOG_H
+#define INGREDIENTMATCHERDIALOG_H
+
+#include "datablocks/element.h"
+#include "datablocks/ingredientlist.h"
+#include "datablocks/recipe.h"
+#include "widgets/recipelistview.h"
+#include "widgets/dblistviewbase.h"
+
+#include <qfontmetrics.h>
+#include <qlabel.h>
+#include <qlistview.h>
+#include <qpushbutton.h>
+#include <qhbox.h>
+#include <qvbox.h>
+
+#include <kstringhandler.h>
+#include <klocale.h>
+
+class KreListView;
+class KIntSpinBox;
+class RecipeDB;
+class MixedNumber;
+
+/**
+@author Unai Garro
+*/
+
+class CustomRecipeListItem : public RecipeListItem
+{
+public:
+ CustomRecipeListItem( QListView* qlv, const Recipe &r, const IngredientList &il ) : RecipeListItem( qlv, r )
+ {
+ ingredientListStored = new QStringList();
+ IngredientList::ConstIterator ili;
+ for ( ili = il.begin();ili != il.end();++ili ) {
+ if ( (*ili).substitutes.count() > 0 ) {
+ QStringList subs;
+ subs << ( *ili ).name;
+ for ( QValueList<IngredientData>::const_iterator it = (*ili).substitutes.begin(); it != (*ili).substitutes.end(); ++it ) {
+ subs << (*it).name;
+ }
+ ingredientListStored->append( subs.join(QString(" %1 ").arg(i18n("OR"))) );
+ }
+ else
+ ingredientListStored->append( ( *ili ).name );
+ }
+
+ moveItem( qlv->lastItem() );
+ }
+ CustomRecipeListItem( QListView* qlv, const Recipe &r ) : RecipeListItem( qlv, r )
+ {
+ ingredientListStored = 0;
+
+ moveItem( qlv->lastItem() );
+ }
+
+ ~CustomRecipeListItem( void )
+ {
+ delete ingredientListStored;
+ }
+
+private:
+ QStringList *ingredientListStored;
+
+public:
+ virtual QString text( int column ) const
+ {
+ if ( column == 2 && ingredientListStored )
+ return ingredientListStored->join ( "," );
+ else
+ return ( RecipeListItem::text( column ) );
+ }
+};
+
+class SectionItem: public QListViewItem
+{
+public:
+ SectionItem( QListView* qlv, QString sectionText ) : QListViewItem( qlv, qlv->lastItem() )
+ {
+ mText = sectionText;
+ }
+
+ ~SectionItem( void )
+ {}
+ virtual void paintCell ( QPainter * p, const QColorGroup & cg, int column, int width, int align );
+
+private:
+ QString mText;
+
+public:
+ virtual QString text( int column ) const
+ {
+ if ( column == 0 )
+ return ( mText );
+ else
+ return ( QString::null );
+ }
+};
+class IngredientMatcherDialog: public QWidget
+{
+
+ Q_OBJECT
+
+public:
+
+ IngredientMatcherDialog( QWidget *parent, RecipeDB* db );
+ ~IngredientMatcherDialog();
+ void reload( ReloadFlags flag = Load );
+
+signals:
+ void recipeSelected( int, int );
+
+private:
+ //Private variables
+ RecipeDB *database;
+
+ //Widgets
+
+ KreListView *allIngListView;
+ KreListView *ingListView;
+
+ KreListView *recipeListView;
+ QHBox *missingBox;
+ QLabel *missingNumberLabel;
+ KIntSpinBox *missingNumberSpinBox;
+
+ QPushButton *okButton;
+ QPushButton *clearButton;
+ QPushButton *addButton;
+ QPushButton *removeButton;
+
+ IngredientList m_ingredientList;
+ QMap<QListViewItem*, IngredientList::iterator> m_item_ing_map;
+
+private slots:
+ void findRecipes( void );
+ void unselectIngredients();
+ void addIngredient();
+ void removeIngredient();
+ void itemRenamed( QListViewItem*, const QPoint &, int col );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/ingredientparserdialog.cpp b/krecipes/src/dialogs/ingredientparserdialog.cpp
new file mode 100644
index 0000000..0e14a8f
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientparserdialog.cpp
@@ -0,0 +1,299 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientparserdialog.h"
+
+#include <qpushbutton.h>
+#include <qtextedit.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qheader.h>
+#include <qapplication.h>
+#include <qclipboard.h>
+#include <qvbox.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <klistview.h>
+#include <kpushbutton.h>
+#include <kmessagebox.h>
+#include <kaction.h>
+#include <kpopupmenu.h>
+
+#include "datablocks/mixednumber.h"
+#include "widgets/inglistviewitem.h"
+
+IngredientParserDialog::IngredientParserDialog( const UnitList &units, QWidget* parent, const char* name )
+ : KDialogBase( parent, name, true, i18n( "Ingredient Parser" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ),
+ m_unitList(units)
+{
+ setButtonBoxOrientation( Vertical );
+
+ QVBox *page = makeVBoxMainWidget();
+
+ textLabel1 = new QLabel( page, "textLabel1" );
+ textLabel1->setTextFormat( QLabel::RichText );
+
+ ingredientTextEdit = new QTextEdit( page, "ingredientTextEdit" );
+ ingredientTextEdit->setTextFormat( QTextEdit::PlainText );
+
+ parseButton = new KPushButton( page, "parseButton" );
+
+ previewLabel = new QLabel( page, "previewLabel" );
+ previewLabel->setTextFormat( QLabel::RichText );
+
+ previewIngView = new KListView( page, "previewIngView" );
+ previewIngView->setSorting(-1);
+ previewIngView->addColumn( i18n( "Ingredient" ) );
+ previewIngView->addColumn( i18n( "Amount" ) );
+ previewIngView->addColumn( i18n( "Unit" ) );
+ previewIngView->addColumn( i18n( "Preparation Method" ) );
+
+ languageChange();
+ setInitialSize( QSize(577, 371).expandedTo(minimumSizeHint()) );
+
+ previewIngView->setItemsRenameable( true );
+ previewIngView->setRenameable( 0, true );
+ previewIngView->setRenameable( 1, true );
+ previewIngView->setRenameable( 2, true );
+ previewIngView->setRenameable( 3, true );
+
+ previewIngView->setSelectionMode( QListView::Extended );
+
+ ingredientTextEdit->setText( QApplication::clipboard()->text() );
+ ingredientTextEdit->selectAll();
+
+ QWidget *buttonWidget = new QWidget( page );
+ QHBoxLayout *buttonBox = new QHBoxLayout(buttonWidget);
+ QSpacerItem *horizontalSpacing = new QSpacerItem( 20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
+ buttonGroup = new QPushButton( i18n("Set &Header"), buttonWidget );
+ QWhatsThis::add( buttonGroup, i18n("If an ingredient header is detected as an ingredient, select it and click this button so that Krecipes will recognize it as a header. All the ingredients below the header will be included within that group.\n\nAlternatively, if you select multiple ingredients and click this button, those ingredients will be grouped together.") );
+ buttonBox->addWidget( buttonGroup );
+ buttonBox->addItem( horizontalSpacing );
+
+ KPopupMenu *kpop = new KPopupMenu( previewIngView );
+ kpop->insertItem( i18n( "&Delete" ), this, SLOT( removeIngredient() ), Key_Delete );
+ kpop->insertItem( i18n("Set &Header") , this, SLOT( convertToHeader() ) );
+
+ connect( parseButton, SIGNAL(clicked()), this, SLOT(parseText()) );
+ connect( buttonGroup, SIGNAL(clicked()), this, SLOT(convertToHeader()) );
+}
+
+IngredientParserDialog::~IngredientParserDialog()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+void IngredientParserDialog::languageChange()
+{
+ textLabel1->setText( i18n( "To use: Paste a list of ingredient below, click \"Parse Text\", and then you may correct any incorrectly parsed ingredients.<br><b>Caution: Fields will be truncated if longer than the database allows</b>" ) );
+ previewLabel->setText( i18n("Ingredients as understood by Krecipes:") );
+ parseButton->setText( i18n( "Parse Text" ) );
+ previewIngView->header()->setLabel( 0, i18n( "Ingredient" ) );
+ previewIngView->header()->setLabel( 1, i18n( "Amount" ) );
+ previewIngView->header()->setLabel( 2, i18n( "Unit" ) );
+ previewIngView->header()->setLabel( 3, i18n( "Preparation Method" ) );
+}
+
+void IngredientParserDialog::accept()
+{
+ for ( QListViewItem *it = previewIngView->firstChild(); it; it = it->nextSibling() ) {
+ if ( it->rtti() == INGGRPLISTVIEWITEM_RTTI ) {
+ QString group = ((IngGrpListViewItem*)it)->group();
+ for ( IngListViewItem *sub_it = (IngListViewItem*)it->firstChild(); sub_it; sub_it = (IngListViewItem*)sub_it->nextSibling() ) {
+ Ingredient ing = sub_it->ingredient();
+ ing.group = group;
+ m_ingList.append(ing);
+ }
+ }
+ else
+ m_ingList.append(((IngListViewItem*)it)->ingredient());
+ }
+
+ QDialog::accept();
+}
+
+void IngredientParserDialog::removeIngredient()
+{
+ delete previewIngView->selectedItem();
+ if ( !previewIngView->firstChild() )
+ enableButtonOK( false );
+}
+
+void IngredientParserDialog::convertToHeader()
+{
+ QPtrList<QListViewItem> items = previewIngView->selectedItems();
+ if ( items.count() == 0 )
+ return;
+ else if ( items.count() > 1 )
+ convertToHeader(items);
+ else { //items.count = 1
+ QListViewItem *item = items.first();
+ if ( item->rtti() == INGLISTVIEWITEM_RTTI ) {
+ QListViewItem *new_item = new IngGrpListViewItem(previewIngView,
+ (item->parent())?item->parent():item,
+ ((IngListViewItem*)item)->ingredient().name, -1);
+
+ QListViewItem *next_sibling;
+ QListViewItem *last_item = 0;
+ for ( QListViewItem * it = (item->parent())?item->nextSibling():new_item->nextSibling(); it; it = next_sibling ) {
+ if ( it->rtti() == INGGRPLISTVIEWITEM_RTTI )
+ break;
+
+ next_sibling = it->nextSibling(); //get the next sibling of this item before we move it
+
+ if ( it->parent() )
+ it->parent()->takeItem(it);
+ else
+ previewIngView->takeItem( it );
+
+ new_item->insertItem( it );
+
+ if ( last_item )
+ it->moveItem( last_item );
+ last_item = it;
+ }
+
+ new_item->setOpen(true);
+
+ delete item;
+ }
+ }
+}
+
+void IngredientParserDialog::convertToHeader( const QPtrList<QListViewItem> &items )
+{
+ if ( items.count() > 0 ) {
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item = it.current();
+
+ if ( item->rtti() != INGLISTVIEWITEM_RTTI )
+ return;
+
+ QString group = ((IngListViewItem*)item)->ingredient().name;
+ QListViewItem *ingGroupItem = new IngGrpListViewItem(previewIngView,
+ (item->parent())?item->parent():item, group, -1);
+ delete item; //delete the ingredient header which was detected as an ingredient
+ ++it;
+
+ QListViewItem *last_item = 0;
+ while ( (item = it.current()) != 0 ) {
+ //ignore anything that isn't an ingredient (e.g. headers)
+ if ( item->rtti() == INGLISTVIEWITEM_RTTI ) {
+ if ( item->parent() )
+ item->parent()->takeItem(item);
+ else
+ previewIngView->takeItem( item );
+
+ ingGroupItem->insertItem( item );
+
+ if ( last_item )
+ item->moveItem( last_item );
+ last_item = item;
+ }
+
+ ++it;
+ }
+
+ ingGroupItem->setOpen(true);
+ previewIngView->clearSelection();
+ }
+}
+
+void IngredientParserDialog::parseText()
+{
+ previewIngView->clear();
+
+ QListViewItem *last_item = 0;
+
+ int line_num = 0;
+ QStringList ingredients = QStringList::split("\n",ingredientTextEdit->text());
+ for ( QStringList::const_iterator it = ingredients.begin(); it != ingredients.end(); ++it ) {
+ QString line = (*it).simplifyWhiteSpace();
+
+ ++line_num;
+ int format_at = 0;
+ Ingredient ing;
+
+
+ //======amount======//
+ int first_space = line.find( " ", format_at+1 );
+ if ( first_space == -1 )
+ first_space = line.length();
+
+ int second_space = line.find( " ", first_space+1 );
+ if ( second_space == -1 )
+ second_space = line.length();
+
+ Ingredient i;
+ bool ok;
+ i.setAmount(line.mid(format_at,second_space-format_at),&ok);
+ if ( !ok ) {
+ i.setAmount(line.mid(format_at,first_space-format_at),&ok);
+ if ( ok ) format_at = first_space+1;
+ }
+ else
+ format_at = second_space+1;
+
+ if ( ok ) {
+ ing.amount = i.amount;
+ ing.amount_offset = i.amount_offset;
+ }
+ else
+ kdDebug()<<"no amount on line "<<line_num<<endl;
+
+
+ //======unit======//
+ first_space = line.find( " ", format_at+1 );
+ if ( first_space == -1 )
+ first_space = line.length();
+
+ bool isUnit = false;
+ QString unitCheck = line.mid(format_at,first_space-format_at);
+
+ for ( UnitList::const_iterator unit_it = m_unitList.begin(); unit_it != m_unitList.end(); ++unit_it ) {
+ if ( (*unit_it).name == unitCheck || (*unit_it).plural == unitCheck || (*unit_it).name_abbrev == unitCheck || (*unit_it).plural_abbrev == unitCheck ) {
+ isUnit = true;
+ format_at = first_space+1;
+ break;
+ }
+ }
+
+ if ( isUnit ) {
+ ing.units.name = unitCheck;
+ ing.units.plural = unitCheck;
+ }
+ else
+ kdDebug()<<"no unit on line "<<line_num<<endl;
+
+
+ //======ingredient======//
+ int ing_end = line.find( QRegExp("(,|;)"), format_at+1 );
+ if ( ing_end == -1 )
+ ing_end = line.length();
+
+ ing.name = line.mid(format_at,ing_end-format_at);
+ format_at = ing_end+2;
+
+
+ //======prep method======//
+ int end = line.length();
+ ing.prepMethodList = ElementList::split(",",line.mid(format_at,end-format_at));
+
+ last_item = new IngListViewItem(previewIngView,last_item,ing);
+ enableButtonOK( true );
+ }
+}
+
+#include "ingredientparserdialog.moc"
diff --git a/krecipes/src/dialogs/ingredientparserdialog.h b/krecipes/src/dialogs/ingredientparserdialog.h
new file mode 100644
index 0000000..948f0cb
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientparserdialog.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTPARSERDIALOG_H
+#define INGREDIENTPARSERDIALOG_H
+
+#include <kdialogbase.h>
+
+#include "datablocks/ingredientlist.h"
+#include "datablocks/unit.h"
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QLabel;
+class QTextEdit;
+class QLineEdit;
+class KPushButton;
+class KListView;
+class QListViewItem;
+class QPushButton;
+
+class IngredientParserDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ IngredientParserDialog( const UnitList &units, QWidget* parent = 0, const char* name = 0 );
+ ~IngredientParserDialog();
+
+ IngredientList ingredients() const { return m_ingList; }
+
+protected:
+ QLabel* textLabel1;
+ QLabel *previewLabel;
+ QTextEdit* ingredientTextEdit;
+ KPushButton* parseButton;
+ KListView* previewIngView;
+ QPushButton* buttonGroup;
+
+protected slots:
+ virtual void accept();
+ void parseText();
+ void removeIngredient();
+
+ //Set Header button slot
+ virtual void convertToHeader();
+ virtual void languageChange();
+
+private:
+ void convertToHeader( const QPtrList<QListViewItem> &items );
+
+ UnitList m_unitList;
+ IngredientList m_ingList;
+};
+
+#endif // INGREDIENTPARSERDIALOG_H
diff --git a/krecipes/src/dialogs/ingredientsdialog.cpp b/krecipes/src/dialogs/ingredientsdialog.cpp
new file mode 100644
index 0000000..231d087
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientsdialog.cpp
@@ -0,0 +1,702 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientsdialog.h"
+#include "backends/recipedb.h"
+#include "createelementdialog.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/mixednumber.h"
+#include "datablocks/weight.h"
+#include "unitsdialog.h"
+#include "usdadatadialog.h"
+#include "selectpropertydialog.h"
+#include "selectunitdialog.h"
+#include "dependanciesdialog.h"
+#include "widgets/ingredientlistview.h"
+#include "widgets/weightinput.h"
+#include "dialogs/ingredientgroupsdialog.h"
+#include "dialogs/createingredientweightdialog.h"
+
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kglobal.h>
+#include <kconfig.h>
+
+#include <qheader.h>
+#include <qmessagebox.h>
+#include <qtabwidget.h>
+
+class WeightListItem : public QListViewItem
+{
+public:
+ WeightListItem( QListView *listview, QListViewItem *item, const Weight &w ) : QListViewItem(listview,item), m_weight(w){}
+
+ void setWeight( const Weight &w ) { m_weight = w; }
+ Weight weight() const { return m_weight; }
+
+ void setAmountUnit( double amount, const Unit &unit, const Element &prepMethod )
+ {
+ m_weight.perAmount = amount;
+ m_weight.perAmountUnitID = unit.id;
+ m_weight.perAmountUnit = (m_weight.perAmount>1)?unit.plural:unit.name;
+ m_weight.prepMethodID = prepMethod.id;
+ m_weight.prepMethod = prepMethod.name;
+ }
+
+ void setWeightUnit( double weight, const Unit &unit )
+ {
+ m_weight.weight = weight;
+ m_weight.weightUnitID = unit.id;
+ m_weight.weightUnit = (m_weight.weight>1)?unit.plural:unit.name;
+ }
+
+ virtual QString text( int c ) const
+ {
+ if ( c == 0 )
+ return QString::number(m_weight.weight)+" "+m_weight.weightUnit;
+ else if ( c == 1 )
+ return QString::number(m_weight.perAmount)+" "
+ +m_weight.perAmountUnit
+ +((m_weight.prepMethodID!=-1)?", "+m_weight.prepMethod:QString::null);
+ else
+ return QString::null;
+ }
+
+private:
+ Weight m_weight;
+};
+
+IngredientsDialog::IngredientsDialog( QWidget* parent, RecipeDB *db ) : QWidget( parent )
+{
+
+ // Store pointer to database
+ database = db;
+
+ // Initialize internal variables
+ propertiesList = new IngredientPropertyList;
+ perUnitListBack = new ElementList;
+
+ // Design dialog
+
+ QHBoxLayout* page_layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ QTabWidget *tabWidget = new QTabWidget( this );
+
+ QWidget *ingredientTab = new QWidget( tabWidget );
+
+ layout = new QGridLayout( ingredientTab, 1, 1, 0, 0 );
+ QSpacerItem* spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+ QSpacerItem* spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+
+ ingredientListView = new KreListView ( ingredientTab, i18n( "Ingredient list" ), true, 0 );
+ StdIngredientListView *list_view = new StdIngredientListView( ingredientListView, database, true );
+ ingredientListView->setListView( list_view );
+ layout->addMultiCellWidget ( ingredientListView, 1, 5, 1, 1 );
+ ingredientListView->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+ QSpacerItem* spacer_rightIngredients = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_rightIngredients, 1, 2 );
+
+
+ addIngredientButton = new QPushButton( ingredientTab );
+ addIngredientButton->setText( "+" );
+ layout->addWidget( addIngredientButton, 1, 3 );
+ addIngredientButton->setMinimumSize( QSize( 30, 30 ) );
+ addIngredientButton->setMaximumSize( QSize( 30, 30 ) );
+ addIngredientButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ addIngredientButton->setFlat( true );
+
+ removeIngredientButton = new QPushButton( ingredientTab );
+ removeIngredientButton->setText( "-" );
+ layout->addWidget( removeIngredientButton, 3, 3 );
+ removeIngredientButton->setMinimumSize( QSize( 30, 30 ) );
+ removeIngredientButton->setMaximumSize( QSize( 30, 30 ) );
+ removeIngredientButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ removeIngredientButton->setFlat( true );
+
+ QSpacerItem* spacer_Ing_Buttons = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_Ing_Buttons, 2, 3 );
+
+
+ QSpacerItem* spacer_Ing_Units = new QSpacerItem( 30, 5, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_Ing_Units, 1, 4 );
+
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+
+ QScrollView *scrollView1 = new QScrollView( ingredientTab, "scrollView1" );
+ scrollView1->enableClipper(true);
+ QWidget *rightWidget = new QWidget(scrollView1);
+ QGridLayout *rightLayout = new QGridLayout( rightWidget, 1, 1, 0, 0 );
+
+ unitsListView = new KreListView ( rightWidget, i18n( "Unit list" ) );
+ unitsListView->listView() ->addColumn( i18n( "Units" ) );
+ unitsListView->listView() ->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+ unitsListView->listView() ->setSorting( 0 );
+ unitsListView->listView() ->setAllColumnsShowFocus( true );
+ rightLayout->addMultiCellWidget ( unitsListView, 1, 4, 0, 0 );
+ unitsListView->listView() ->setMinimumWidth( 150 );
+ unitsListView->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+ QSpacerItem* spacer_rightUnits = new QSpacerItem( 5, 5, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ rightLayout->addItem( spacer_rightUnits, 1, 1 );
+
+ addUnitButton = new QPushButton( rightWidget );
+ addUnitButton->setText( "+" );
+ rightLayout->addWidget( addUnitButton, 1, 2 );
+ addUnitButton->resize( QSize( 30, 30 ) );
+ addUnitButton->setMinimumSize( QSize( 30, 30 ) );
+ addUnitButton->setMaximumSize( QSize( 30, 30 ) );
+ addUnitButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ addUnitButton->setFlat( true );
+
+ removeUnitButton = new QPushButton( rightWidget );
+ removeUnitButton->setText( "-" );
+ rightLayout->addWidget( removeUnitButton, 3, 2 );
+ removeUnitButton->resize( QSize( 30, 30 ) );
+ removeUnitButton->setMinimumSize( QSize( 30, 30 ) );
+ removeUnitButton->setMaximumSize( QSize( 30, 30 ) );
+ removeUnitButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ removeUnitButton->setFlat( true );
+ QSpacerItem* spacer_Units_Properties = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ rightLayout->addItem( spacer_Units_Properties, 2, 2 );
+
+
+ propertiesListView = new KreListView ( rightWidget, i18n( "Ingredient Properties" ) );
+ rightLayout->addMultiCellWidget ( propertiesListView, 6, 9, 0, 0 );
+
+ propertiesListView->listView() ->addColumn( i18n( "Property" ) );
+ propertiesListView->listView() ->addColumn( i18n( "Amount" ) );
+ propertiesListView->listView() ->addColumn( i18n( "Units" ) );
+ propertiesListView->listView() ->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+ propertiesListView->listView() ->setAllColumnsShowFocus( true );
+ propertiesListView->listView() ->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+ propertiesListView->listView() ->setSorting( -1 ); // Disable sorting. For the moment, the order is important to identify the per_units ID corresponding to this row. So the user shouldn't change this order.
+
+ addPropertyButton = new QPushButton( rightWidget );
+ addPropertyButton->setText( "+" );
+ rightLayout->addWidget( addPropertyButton, 6, 2 );
+ addPropertyButton->resize( QSize( 30, 30 ) );
+ addPropertyButton->setMinimumSize( QSize( 30, 30 ) );
+ addPropertyButton->setMaximumSize( QSize( 30, 30 ) );
+ addPropertyButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ addPropertyButton->setFlat( true );
+
+ removePropertyButton = new QPushButton( rightWidget );
+ removePropertyButton->setText( "-" );
+ rightLayout->addWidget( removePropertyButton, 8, 2 );
+ removePropertyButton->resize( QSize( 30, 30 ) );
+ removePropertyButton->setMinimumSize( QSize( 30, 30 ) );
+ removePropertyButton->setMaximumSize( QSize( 30, 30 ) );
+ removePropertyButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ removePropertyButton->setFlat( true );
+
+ QSpacerItem* spacer_Prop_Buttons = new QSpacerItem( 9, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ rightLayout->addItem( spacer_Prop_Buttons, 7, 2 );
+
+ weightsListView = new KreListView ( rightWidget, i18n( "Ingredient Weights" ) );
+ weightsListView->listView() ->addColumn( i18n( "Weight" ) );
+ weightsListView->listView() ->addColumn( i18n( "Per Amount" ) );
+ weightsListView->listView() ->setAllColumnsShowFocus( true );
+ rightLayout->addMultiCellWidget ( weightsListView, 10, 14, 0, 0 );
+ weightsListView->listView() ->setMinimumWidth( 150 );
+ weightsListView->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+ //QSpacerItem* spacer_rightWeights = new QSpacerItem( 5, 5, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ //layout->addItem( spacer_rightWeights, 1, 6 );
+
+ addWeightButton = new QPushButton( rightWidget );
+ addWeightButton->setText( "+" );
+ rightLayout->addWidget( addWeightButton, 10, 2 );
+ addWeightButton->resize( QSize( 30, 30 ) );
+ addWeightButton->setMinimumSize( QSize( 30, 30 ) );
+ addWeightButton->setMaximumSize( QSize( 30, 30 ) );
+ addWeightButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ addWeightButton->setFlat( true );
+
+ removeWeightButton = new QPushButton( rightWidget );
+ removeWeightButton->setText( "-" );
+ rightLayout->addWidget( removeWeightButton, 12, 2 );
+ removeWeightButton->resize( QSize( 30, 30 ) );
+ removeWeightButton->setMinimumSize( QSize( 30, 30 ) );
+ removeWeightButton->setMaximumSize( QSize( 30, 30 ) );
+ removeWeightButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ removeWeightButton->setFlat( true );
+
+ QSpacerItem* spacer_Weight_Properties = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ rightLayout->addItem( spacer_Weight_Properties, 11, 2 );
+
+ QSpacerItem* spacerBottom = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Expanding );
+ rightLayout->addItem( spacerBottom, 13, 2 );
+
+ scrollView1->setResizePolicy( QScrollView::AutoOneFit );
+ layout->addMultiCellWidget(scrollView1,1,4,5,5);
+
+ QPushButton *loadUsdaButton = new QPushButton( ingredientTab );
+ loadUsdaButton->setText( i18n( "Load USDA data" ) );
+ loadUsdaButton->setFlat( true );
+ layout->addWidget(loadUsdaButton,5,5);
+
+ scrollView1->addChild(rightWidget);
+ scrollView1->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Ignored );
+
+
+ inputBox = new KDoubleNumInput( propertiesListView->listView() ->viewport() );
+ propertiesListView->listView() ->addChild( inputBox );
+ inputBox->hide();
+
+ tabWidget->insertTab( ingredientTab, i18n( "Ingredients" ) );
+
+ groupsDialog = new IngredientGroupsDialog(database,tabWidget,"groupsDialog");
+ tabWidget->insertTab( groupsDialog, i18n( "Headers" ) );
+
+ page_layout->addWidget( tabWidget );
+
+ // Initialize
+ unitList = new UnitList;
+
+ // Signals & Slots
+ connect( ingredientListView->listView(), SIGNAL( selectionChanged() ), this, SLOT( updateLists() ) );
+ connect( addIngredientButton, SIGNAL( clicked() ), list_view, SLOT( createNew() ) );
+ connect( addUnitButton, SIGNAL( clicked() ), this, SLOT( addUnitToIngredient() ) );
+ connect( removeUnitButton, SIGNAL( clicked() ), this, SLOT( removeUnitFromIngredient() ) );
+ connect( addWeightButton, SIGNAL( clicked() ), this, SLOT( addWeight() ) );
+ connect( removeWeightButton, SIGNAL( clicked() ), this, SLOT( removeWeight() ) );
+ connect( removeIngredientButton, SIGNAL( clicked() ), list_view, SLOT( remove
+ () ) );
+ connect( addPropertyButton, SIGNAL( clicked() ), this, SLOT( addPropertyToIngredient() ) );
+ connect( removePropertyButton, SIGNAL( clicked() ), this, SLOT( removePropertyFromIngredient() ) );
+ connect( propertiesListView->listView(), SIGNAL( executed( QListViewItem* ) ), this, SLOT( insertPropertyEditBox( QListViewItem* ) ) );
+ connect( propertiesListView->listView(), SIGNAL( selectionChanged() ), inputBox, SLOT( hide() ) );
+ connect( inputBox, SIGNAL( valueChanged( double ) ), this, SLOT( setPropertyAmount( double ) ) );
+
+ connect( weightsListView->listView(), SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), SLOT( itemRenamed( QListViewItem*, const QPoint &, int ) ) );
+
+ connect( loadUsdaButton, SIGNAL( clicked() ), this, SLOT( openUSDADialog() ) );
+}
+
+
+IngredientsDialog::~IngredientsDialog()
+{
+ delete unitList;
+ delete perUnitListBack;
+ delete propertiesList;
+}
+
+void IngredientsDialog::reloadIngredientList( ReloadFlags flag )
+{
+ ( ( StdIngredientListView* ) ingredientListView->listView() ) ->reload(flag);
+
+ // Reload Unit List
+ updateLists();
+
+}
+
+void IngredientsDialog::reloadUnitList()
+{
+
+ int ingredientID = -1;
+ // Find selected ingredient
+ QListViewItem *it;
+ it = ingredientListView->listView() ->selectedItem();
+
+ if ( it ) { // Check if an ingredient is selected first
+ ingredientID = it->text( 1 ).toInt();
+ }
+
+
+ unitList->clear();
+ unitsListView->listView() ->clear();
+
+ if ( ingredientID >= 0 ) {
+ database->loadPossibleUnits( ingredientID, unitList );
+
+ //Populate this data into the KListView
+
+ for ( UnitList::const_iterator unit_it = unitList->begin(); unit_it != unitList->end(); ++unit_it ) {
+ if ( !( *unit_it ).name.isEmpty() ) {
+ ( void ) new QListViewItem( unitsListView->listView(), ( *unit_it ).name, QString::number( ( *unit_it ).id ) );
+ }
+ }
+
+ // Select the first unit
+ unitsListView->listView() ->setSelected( unitsListView->listView() ->firstChild(), true );
+
+ }
+}
+
+void IngredientsDialog::addWeight()
+{
+ QListViewItem *it = ingredientListView->listView()->selectedItem();
+ if ( it ) {
+ CreateIngredientWeightDialog weightDialog( this, database );
+ if ( weightDialog.exec() == QDialog::Accepted ) {
+ Weight w = weightDialog.weight();
+ w.ingredientID = it->text( 1 ).toInt();
+ database->addIngredientWeight( w );
+
+ QListViewItem * lastElement = weightsListView->listView()->lastItem();
+
+ WeightListItem *weight_it = new WeightListItem( weightsListView->listView(), lastElement, w );
+ weight_it->setAmountUnit( w.perAmount, database->unitName(w.perAmountUnitID),
+ Element(w.prepMethod,w.prepMethodID)
+ );
+ weight_it->setWeightUnit( w.weight, database->unitName(w.weightUnitID) );
+ }
+ }
+}
+
+void IngredientsDialog::removeWeight()
+{
+ QListViewItem *it = weightsListView->listView() ->selectedItem();
+ if ( it ) {
+ switch ( KMessageBox::warningContinueCancel(this, i18n("Recipes may require this information for nutrient analysis. Are you sure you want to delete this entry?"), QString::null, KStdGuiItem::cont(), "DeleteIngredientWeight") ) {
+ case KMessageBox::Continue:
+ database->removeIngredientWeight( ((WeightListItem*)it)->weight().id );
+ delete it;
+ break;
+ default: break;
+ }
+ }
+}
+
+void IngredientsDialog::itemRenamed( QListViewItem* item, const QPoint &, int col )
+{
+ WeightListItem *weight_it = (WeightListItem*)item;
+ Weight w = weight_it->weight();
+
+ if ( col == 0 ) {
+ KDialogBase amountEditDialog(this,"WeightAmountEdit",
+ false, i18n("Enter amount"), KDialogBase::Cancel | KDialogBase::Ok, KDialogBase::Ok);
+
+ QGroupBox *box = new QGroupBox( 1, Horizontal, i18n("Amount"), &amountEditDialog );
+ AmountUnitInput *amountEdit = new AmountUnitInput( box, database, Unit::Mass, MixedNumber::DecimalFormat );
+
+ WeightListItem *it = (WeightListItem*)item;
+ Weight w = it->weight();
+
+ amountEdit->setAmount( w.weight );
+ amountEdit->setUnit( Unit(w.weightUnit,w.weightUnit,w.weightUnitID) );
+
+ amountEditDialog.setMainWidget(box);
+
+ if ( amountEditDialog.exec() == QDialog::Accepted ) {
+ MixedNumber amount = amountEdit->amount();
+ Unit unit = amountEdit->unit();
+
+ it->setWeightUnit( amount.toDouble(), unit );
+ database->addIngredientWeight( it->weight() );
+ }
+ }
+ else if ( col == 1 ) {
+ KDialogBase amountEditDialog(this,"PerAmountEdit",
+ false, i18n("Enter amount"), KDialogBase::Cancel | KDialogBase::Ok, KDialogBase::Ok);
+
+ QGroupBox *box = new QGroupBox( 1, Horizontal, i18n("Amount"), &amountEditDialog );
+ WeightInput *amountEdit = new WeightInput( box, database, Unit::All, MixedNumber::DecimalFormat );
+
+ WeightListItem *it = (WeightListItem*)item;
+ Weight w = it->weight();
+
+ amountEdit->setAmount( w.perAmount );
+ amountEdit->setUnit( Unit(w.perAmountUnit,w.perAmountUnit,w.perAmountUnitID) );
+ amountEdit->setPrepMethod( Element(w.prepMethod,w.prepMethodID) );
+
+ amountEditDialog.setMainWidget(box);
+
+ if ( amountEditDialog.exec() == QDialog::Accepted ) {
+ MixedNumber amount = amountEdit->amount();
+ Unit unit = amountEdit->unit();
+
+ it->setAmountUnit( amount.toDouble(), unit, amountEdit->prepMethod() );
+ database->addIngredientWeight( it->weight() );
+ }
+ }
+}
+
+void IngredientsDialog::addUnitToIngredient( void )
+{
+
+ // Find selected ingredient item
+ QListViewItem * it;
+ int ingredientID = -1;
+ if ( ( it = ingredientListView->listView() ->selectedItem() ) ) {
+ ingredientID = it->text( 1 ).toInt();
+ }
+ if ( ingredientID >= 0 ) // an ingredient was selected previously
+ {
+ UnitList allUnits;
+ database->loadUnits( &allUnits );
+
+ SelectUnitDialog unitsDialog( this, allUnits, SelectUnitDialog::HideEmptyUnit );
+
+ if ( unitsDialog.exec() == QDialog::Accepted )
+ {
+ int unitID = unitsDialog.unitID();
+
+ if ( !( database->ingredientContainsUnit( ingredientID, unitID ) ) )
+ database->addUnitToIngredient( ingredientID, unitID ); // Add chosen unit to ingredient in database
+ else {
+ QMessageBox::information( this, i18n( "Unit Exists" ), i18n( "The ingredient contains already the unit that you have chosen." ) );
+ }
+ reloadUnitList(); // Reload the list from database
+ }
+ }
+}
+
+void IngredientsDialog::removeUnitFromIngredient( void )
+{
+
+ // Find selected ingredient/unit item combination
+ QListViewItem * it;
+ int ingredientID = -1, unitID = -1;
+ if ( ( it = ingredientListView->listView() ->selectedItem() ) )
+ ingredientID = it->text( 1 ).toInt();
+ if ( ( it = unitsListView->listView() ->selectedItem() ) )
+ unitID = it->text( 1 ).toInt();
+
+ if ( ( ingredientID >= 0 ) && ( unitID >= 0 ) ) // an ingredient/unit combination was selected previously
+ {
+ ElementList dependingRecipes, dependingPropertiesInfo;
+
+ database->findIngredientUnitDependancies( ingredientID, unitID, &dependingRecipes, &dependingPropertiesInfo );
+
+ QValueList<ListInfo> lists;
+ if ( !dependingRecipes.isEmpty() ) {
+ ListInfo info;
+ info.list = dependingRecipes;
+ info.name = i18n("Recipes");
+ lists << info;
+ }
+ if ( !dependingPropertiesInfo.isEmpty() ) {
+ ListInfo info;
+ info.list = dependingPropertiesInfo;
+ info.name = i18n("Properties");
+ lists << info;
+ }
+
+ if ( lists.isEmpty() )
+ database->removeUnitFromIngredient( ingredientID, unitID );
+ else
+ { // must warn!
+ DependanciesDialog warnDialog( this, lists );
+ if ( !dependingRecipes.isEmpty() )
+ warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") );
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removeUnitFromIngredient( ingredientID, unitID );
+ }
+ reloadUnitList(); // Reload the list from database
+ reloadPropertyList(); // Properties could have been removed if a unit is removed, so we need to reload.
+ }
+}
+
+void IngredientsDialog:: reloadPropertyList( void )
+{
+ propertiesList->clear();
+ propertiesListView->listView() ->clear();
+ perUnitListBack->clear();
+
+ inputBox->hide();
+
+
+ //If none is selected, select first item
+ QListViewItem *it;
+ it = ingredientListView->listView() ->selectedItem();
+
+ //Populate this data into the KListView
+ if ( it ) { // make sure that the ingredient list is not empty
+
+ database->loadProperties( propertiesList, it->text( 1 ).toInt() ); // load the list for this ingredient
+ for ( IngredientPropertyList::const_iterator prop_it = propertiesList->begin(); prop_it != propertiesList->end(); ++prop_it ) {
+ QListViewItem * lastElement = propertiesListView->listView() ->lastItem();
+ //Insert property after the last one (it's important to keep the order in the case of the properties to be able to identify the per_units ID later on).
+ ( void ) new QListViewItem( propertiesListView->listView(), lastElement, (*prop_it).name, QString::number( (*prop_it).amount ), (*prop_it).units + QString( "/" ) + (*prop_it).perUnit.name, QString::number( (*prop_it).id ) );
+ // Store the perUnits with the ID for using later
+ Element perUnitEl;
+ perUnitEl.id = (*prop_it).perUnit.id;
+ perUnitEl.name = (*prop_it).perUnit.name;
+ perUnitListBack->append( perUnitEl );
+
+ }
+ }
+}
+
+void IngredientsDialog::reloadWeightList( void )
+{
+ weightsListView->listView() ->clear();
+
+ //If none is selected, select first item
+ QListViewItem *it = ingredientListView->listView() ->selectedItem();
+
+ //Populate this data into the KListView
+ if ( it ) { // make sure that the ingredient list is not empty
+ WeightList list = database->ingredientWeightUnits( it->text( 1 ).toInt() ); // load the list for this ingredient
+ for ( WeightList::const_iterator weight_it = list.begin(); weight_it != list.end(); ++weight_it ) {
+ QListViewItem * lastElement = weightsListView->listView() ->lastItem();
+
+ Weight w = *weight_it;
+ WeightListItem *weight_it = new WeightListItem( weightsListView->listView(), lastElement, w );
+ weight_it->setAmountUnit( w.perAmount,
+ database->unitName(w.perAmountUnitID),
+ Element((w.prepMethodID==-1)?QString::null:database->prepMethodName(w.prepMethodID),w.prepMethodID)
+ );
+ weight_it->setWeightUnit( w.weight, database->unitName(w.weightUnitID) );
+ }
+ }
+}
+
+void IngredientsDialog:: updateLists( void )
+{
+ reloadUnitList();
+ reloadPropertyList();
+ reloadWeightList();
+}
+
+void IngredientsDialog::addPropertyToIngredient( void )
+{
+
+ // Find selected ingredient item
+ QListViewItem * it;
+ int ingredientID = -1;
+ if ( ( it = ingredientListView->listView() ->selectedItem() ) ) {
+ ingredientID = it->text( 1 ).toInt();
+ }
+ if ( ingredientID >= 0 ) // an ingredient was selected previously
+ {
+ IngredientPropertyList allProperties;
+ database->loadProperties( &allProperties );
+ UnitList unitList;
+ database->loadPossibleUnits( ingredientID, &unitList );
+ SelectPropertyDialog propertyDialog( this, &allProperties, &unitList, SelectPropertyDialog::HideEmptyUnit );
+
+ if ( propertyDialog.exec() == QDialog::Accepted )
+ {
+
+ int propertyID = propertyDialog.propertyID();
+ int perUnitsID = propertyDialog.perUnitsID();
+ if ( !( database->ingredientContainsProperty( ingredientID, propertyID, perUnitsID ) ) ) {
+ if ( ( propertyID >= 0 ) && ( perUnitsID >= 0 ) ) // check if the property is not -1 ... (not selected)
+ database->addPropertyToIngredient( ingredientID, propertyID, 0, perUnitsID ); // Add result chosen property to ingredient in database, with amount 0 by default
+ }
+ else {
+ QMessageBox::information( this, i18n( "Property Exists" ), i18n( "The property you tried to add already exists in the ingredient with the same per units." ) );
+ }
+ reloadPropertyList(); // Reload the list from database
+ }
+ }
+}
+
+void IngredientsDialog::removePropertyFromIngredient( void )
+{
+
+ // Find selected ingredient/property item combination
+ QListViewItem * it;
+ int ingredientID = -1, propertyID = -1;
+ int perUnitsID = -1;
+ if ( ( it = ingredientListView->listView() ->selectedItem() ) )
+ ingredientID = it->text( 1 ).toInt();
+ if ( ( it = propertiesListView->listView() ->selectedItem() ) )
+ propertyID = it->text( 3 ).toInt();
+ if ( propertyID >= 0 )
+ perUnitsID = perUnitListBack->getElement( findPropertyNo( it ) ).id ;
+
+ if ( ( ingredientID >= 0 ) && ( propertyID >= 0 ) && ( perUnitsID >= 0 ) ) // an ingredient/property combination was selected previously
+ {
+ ElementList results;
+ database->removePropertyFromIngredient( ingredientID, propertyID, perUnitsID );
+
+ reloadPropertyList(); // Reload the list from database
+
+ }
+}
+
+void IngredientsDialog::insertPropertyEditBox( QListViewItem* it )
+{
+ QRect r = propertiesListView->listView() ->header() ->sectionRect( 1 );
+
+ r.moveBy( 0, propertiesListView->listView() ->itemRect( it ).y() ); //Move down to the item, note that its height is same as header's right now.
+
+ r.setHeight( it->height() ); // Set the item's height
+
+ inputBox->setGeometry( r );
+
+ inputBox->setValue( it->text( 1 ).toDouble() );
+ inputBox->show();
+}
+
+void IngredientsDialog::setPropertyAmount( double amount )
+{
+ QListViewItem *ing_it = ingredientListView->listView() ->selectedItem(); // Find selected ingredient
+ QListViewItem *prop_it = propertiesListView->listView() ->selectedItem();
+
+ if ( ing_it && prop_it ) // Appart from property, Check if an ingredient is selected first, just in case
+ {
+ prop_it->setText( 1, QString::number( amount ) );
+ int propertyID = prop_it->text( 3 ).toInt();
+ int ingredientID = ing_it->text( 1 ).toInt();
+ int per_units = perUnitListBack->getElement( findPropertyNo( prop_it ) ).id ;
+ database->changePropertyAmountToIngredient( ingredientID, propertyID, amount, per_units );
+ }
+}
+
+int IngredientsDialog::findPropertyNo( QListViewItem * /*it*/ )
+{
+ bool found = false;
+ int i = 0;
+ QListViewItem* item = propertiesListView->listView() ->firstChild();
+ while ( i < propertiesListView->listView() ->childCount() && !found ) {
+ if ( item == propertiesListView->listView() ->currentItem() )
+ found = true;
+ else {
+ item = item->nextSibling();
+ ++i;
+ }
+ }
+ if ( found ) {
+ return ( i );
+ }
+ else {
+ return ( -1 );
+ }
+}
+
+void IngredientsDialog::reload( ReloadFlags flag )
+{
+ reloadIngredientList( flag );
+ groupsDialog->reload( flag );
+}
+
+void IngredientsDialog::openUSDADialog( void )
+{
+ QListViewItem * ing_it = ingredientListView->listView() ->selectedItem(); // Find selected ingredient
+ if ( ing_it ) {
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+ USDADataDialog usda_dialog( Element( ing_it->text( 0 ), ing_it->text( 1 ).toInt() ), database, this );
+ KApplication::restoreOverrideCursor();
+
+ if ( usda_dialog.exec() == QDialog::Accepted ) {
+ reloadPropertyList(); //update property list upon success
+ reloadWeightList();
+ }
+ }
+ else
+ QMessageBox::information( this, QString::null, i18n( "No ingredient selected." ) );
+}
+
+#include "ingredientsdialog.moc"
diff --git a/krecipes/src/dialogs/ingredientsdialog.h b/krecipes/src/dialogs/ingredientsdialog.h
new file mode 100644
index 0000000..0701090
--- /dev/null
+++ b/krecipes/src/dialogs/ingredientsdialog.h
@@ -0,0 +1,93 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTSDIALOG_H
+#define INGREDIENTSDIALOG_H
+
+#include <qpushbutton.h>
+#include <qvbox.h>
+#include <qlayout.h>
+#include <qwidget.h>
+#include <knuminput.h>
+
+#include "widgets/krelistview.h"
+#include "widgets/dblistviewbase.h"
+#include "datablocks/unit.h"
+
+class KDoubleNumInput;
+
+class RecipeDB;
+class ElementList;
+class SelectUnitDialog;
+class CreateElementDialog;
+class IngredientPropertyList;
+class SelectPropertyDialog;
+class UnitsDialog;
+class IngredientGroupsDialog;
+class MixedNumber;
+
+class IngredientsDialog: public QWidget
+{
+Q_OBJECT
+
+public:
+ IngredientsDialog( QWidget* parent, RecipeDB *db );
+ ~IngredientsDialog();
+ void reload( ReloadFlags flag = Load );
+
+private:
+ // Widgets
+ QGridLayout* layout;
+ QPushButton* addIngredientButton;
+ QPushButton* removeIngredientButton;
+ QPushButton* addUnitButton;
+ QPushButton* removeUnitButton;
+ QPushButton* addPropertyButton;
+ QPushButton* removePropertyButton;
+ QPushButton* addWeightButton;
+ QPushButton* removeWeightButton;
+ KreListView* ingredientListView;
+ KreListView* unitsListView;
+ KreListView* propertiesListView;
+ KreListView* weightsListView;
+ QPushButton* pushButton5;
+ KDoubleNumInput* inputBox;
+ IngredientGroupsDialog *groupsDialog;
+
+ // Internal Methods
+ void reloadIngredientList( ReloadFlags flag = Load );
+ void reloadUnitList( void );
+ void reloadPropertyList( void );
+ void reloadWeightList( void );
+ int findPropertyNo( QListViewItem *it );
+
+ // Internal Variables
+ RecipeDB *database;
+ UnitList *unitList;
+ IngredientPropertyList *propertiesList;
+ ElementList *perUnitListBack;
+
+private slots:
+ void addUnitToIngredient( void );
+ void removeUnitFromIngredient( void );
+ void addWeight();
+ void removeWeight();
+ void updateLists( void );
+ void addPropertyToIngredient( void );
+ void removePropertyFromIngredient( void );
+ void insertPropertyEditBox( QListViewItem* it );
+ void setPropertyAmount( double amount );
+ void openUSDADialog( void );
+ void itemRenamed( QListViewItem*, const QPoint &, int col );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/pagesetupdialog.cpp b/krecipes/src/dialogs/pagesetupdialog.cpp
new file mode 100644
index 0000000..05d81bb
--- /dev/null
+++ b/krecipes/src/dialogs/pagesetupdialog.cpp
@@ -0,0 +1,310 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "pagesetupdialog.h"
+
+#include <qdir.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qfileinfo.h>
+#include <qpushbutton.h>
+#include <qpopupmenu.h>
+#include <qtooltip.h>
+#include <qtabwidget.h>
+#include <qlabel.h>
+
+#include <khtmlview.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+#include <kstandarddirs.h>
+#include <kaction.h>
+#include <kconfig.h>
+#include <kstdaction.h>
+#include <ktoolbar.h>
+#include <kpopupmenu.h>
+
+#include "setupdisplay.h"
+
+PageSetupDialog::PageSetupDialog( QWidget *parent, const Recipe &sample, const QString &configEntry ) : KDialog( parent, 0, true ), m_configEntry(configEntry)
+{
+ KIconLoader il;
+
+ QVBoxLayout * layout = new QVBoxLayout( this );
+
+ KToolBar *toolbar = new KToolBar( this );
+ KActionCollection *actionCollection = new KActionCollection( this );
+
+ KAction *std_open = KStdAction::open( 0, 0, 0 ); //use this to create a custom open action
+ KToolBarPopupAction *custom_open = new KToolBarPopupAction( std_open->text(), std_open->icon(), std_open->shortcut(), this, SLOT( loadFile() ), actionCollection, "open_popup" );
+
+ KPopupMenu *open_popup = custom_open->popupMenu();
+
+ open_popup->insertTitle( i18n( "Styles" ) );
+ QDir included_layouts( getIncludedLayoutDir(), "*.klo", QDir::Name | QDir::IgnoreCase, QDir::Files );
+ for ( unsigned int i = 0; i < included_layouts.count(); i++ ) {
+ int id = open_popup->insertItem( included_layouts[ i ].left(included_layouts[ i ].find(".")), this, SLOT( loadLayout( int ) ) );
+ included_layouts_map.insert( id, included_layouts[ i ] );
+ }
+
+ open_popup->insertTitle( i18n( "Templates" ) );
+ QDir included_templates( getIncludedLayoutDir(), "*.template", QDir::Name | QDir::IgnoreCase, QDir::Files );
+ for ( unsigned int i = 0; i < included_templates.count(); i++ ) {
+ int id = open_popup->insertItem( included_templates[ i ].left(included_templates[ i ].find(".")).replace("_"," "), this, SLOT( loadTemplate( int ) ) );
+ included_layouts_map.insert( id, included_templates[ i ] );
+ }
+
+ custom_open->plug( toolbar );
+
+ KStdAction::save( this, SLOT( saveLayout() ), actionCollection ) ->plug( toolbar );
+ KStdAction::saveAs( this, SLOT( saveAsLayout() ), actionCollection ) ->plug( toolbar );
+ KStdAction::redisplay( this, SLOT( reloadLayout() ), actionCollection ) ->plug( toolbar );
+
+ KToolBarPopupAction *shown_items = new KToolBarPopupAction( i18n( "Items Shown" ), "frame_edit" );
+ shown_items->setDelayed( false );
+ shown_items_popup = shown_items->popupMenu();
+ shown_items_popup->insertTitle( i18n( "Show Items" ) );
+ shown_items->plug( toolbar );
+ layout->addWidget( toolbar );
+
+ QLabel *help = new QLabel(i18n("<i>Usage: Right-click any element to edit the look of that element.</i>"),this);
+ layout->addWidget( help );
+
+ m_htmlPart = new SetupDisplay(sample, this);
+ layout->addWidget( m_htmlPart->view() );
+
+ QHBox *buttonsBox = new QHBox( this );
+ QPushButton *okButton = new QPushButton( il.loadIconSet( "ok", KIcon::Small ), i18n( "Save and Close" ), buttonsBox );
+ QPushButton *cancelButton = new QPushButton( il.loadIconSet( "cancel", KIcon::Small ), i18n( "&Cancel" ), buttonsBox );
+ layout->addWidget( buttonsBox );
+
+ connect( m_htmlPart, SIGNAL(itemVisibilityChanged(KreDisplayItem*,bool)), this, SLOT(updateItemVisibility(KreDisplayItem*,bool)) );
+ connect( okButton, SIGNAL( clicked() ), SLOT( accept() ) );
+ connect( cancelButton, SIGNAL( clicked() ), SLOT( reject() ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Page Setup" );
+ QSize defaultSize(300,400);
+ resize(config->readSizeEntry( "WindowSize", &defaultSize ));
+
+ //let's do everything we can to be sure at least some layout is loaded
+ QString layoutFile = config->readEntry( m_configEntry+"Layout", locate( "appdata", "layouts/Default.klo" ) );
+ if ( layoutFile.isEmpty() || !QFile::exists( layoutFile ) )
+ layoutFile = locate( "appdata", "layouts/Default.klo" );
+
+ QString tmpl = config->readEntry( m_configEntry+"Template", locate( "appdata", "layouts/Default.template" ) );
+ if ( tmpl.isEmpty() || !QFile::exists( tmpl ) )
+ tmpl = locate( "appdata", "layouts/Default.template" );
+ kdDebug()<<"tmpl: "<<tmpl<<endl;
+ active_template = tmpl;
+ loadLayout( layoutFile );
+
+ initShownItems();
+}
+
+void PageSetupDialog::accept()
+{
+ if ( m_htmlPart->hasChanges() )
+ saveLayout();
+
+ if ( !active_filename.isEmpty() ) {
+ KConfig * config = kapp->config();
+ config->setGroup( "Page Setup" );
+ config->writeEntry( m_configEntry+"Layout", active_filename );
+ }
+
+ if ( !active_template.isEmpty() ) {
+ KConfig * config = kapp->config();
+ config->setGroup( "Page Setup" );
+ config->writeEntry( m_configEntry+"Template", active_template );
+ }
+
+ KConfig *config = kapp->config();
+ config->setGroup( "Page Setup" );
+ config->writeEntry( "WindowSize", size() );
+
+ QDialog::accept();
+}
+
+void PageSetupDialog::reject()
+{
+ if ( m_htmlPart->hasChanges() ) {
+ switch ( KMessageBox::questionYesNoCancel( this, i18n( "The recipe view layout has been modified.\nDo you want to save it?" ), i18n( "Save Layout?" ) ) ) {
+ case KMessageBox::Yes:
+ saveLayout();
+ break;
+ case KMessageBox::No:
+ break;
+ default:
+ return ;
+ }
+ }
+
+ QDialog::reject();
+}
+
+void PageSetupDialog::updateItemVisibility( KreDisplayItem *item, bool visible )
+{
+ shown_items_popup->setItemChecked( widget_popup_map[ item ], visible );
+}
+
+void PageSetupDialog::initShownItems()
+{
+ shown_items_popup->clear();
+
+ PropertiesMap properties = m_htmlPart->properties();
+
+ QValueList<QString> nameList;
+ QMap<QString,KreDisplayItem*> nameMap;
+
+ for ( PropertiesMap::const_iterator it = properties.begin(); it != properties.end(); ++it ) {
+ nameList << it.key()->name;
+ nameMap.insert( it.key()->name, it.key() );
+ }
+ qHeapSort( nameList );
+
+ for ( QValueList<QString>::const_iterator it = nameList.begin(); it != nameList.end(); ++it ) {
+ KreDisplayItem *item = nameMap[*it];
+ if ( properties[item] & SetupDisplay::Visibility ) {
+ int new_id = shown_items_popup->insertItem ( *it );
+ shown_items_popup->setItemChecked( new_id, item->show );
+ shown_items_popup->connectItem( new_id, this, SLOT( setItemShown( int ) ) );
+
+ popup_widget_map.insert( new_id, item );
+ widget_popup_map.insert( item, new_id );
+ }
+ }
+}
+
+void PageSetupDialog::setItemShown( int id )
+{
+ shown_items_popup->setItemChecked( id, !shown_items_popup->isItemChecked( id ) );
+ m_htmlPart->setItemShown( popup_widget_map[ id ], shown_items_popup->isItemChecked( id ) );
+}
+
+void PageSetupDialog::loadFile()
+{
+ QString file = KFileDialog::getOpenFileName( locateLocal( "appdata", "layouts/" ), QString("*.klo *.template|%1").arg(i18n("Krecipes style or template file")), this, i18n( "Select Layout" ) );
+
+ if ( file.endsWith(".klo") )
+ loadLayout( file );
+ else {
+ active_template = file;
+ m_htmlPart->loadTemplate( file );
+ }
+}
+
+void PageSetupDialog::loadTemplate( int popup_param )
+{
+ active_template = getIncludedLayoutDir() + "/" + included_layouts_map[ popup_param ];
+ m_htmlPart->loadTemplate( active_template );
+}
+
+void PageSetupDialog::loadLayout( int popup_param )
+{
+ loadLayout( getIncludedLayoutDir() + "/" + included_layouts_map[ popup_param ] );
+}
+
+void PageSetupDialog::loadLayout( const QString &filename )
+{
+ if ( m_htmlPart->hasChanges() ) {
+ switch ( KMessageBox::questionYesNoCancel( this, i18n( "This layout has been modified.\nDo you want to save it?" ), i18n( "Save Layout?" ) ) ) {
+ case KMessageBox::Yes:
+ saveLayout();
+ break;
+ case KMessageBox::No:
+ break;
+ default:
+ return ;
+ }
+ }
+
+ if ( !filename.isEmpty() ) {
+ m_htmlPart->loadLayout( filename );
+ setActiveFile( filename );
+ }
+}
+
+void PageSetupDialog::reloadLayout()
+{
+ m_htmlPart->reload();
+}
+
+void PageSetupDialog::saveLayout()
+{
+ if ( m_htmlPart->hasChanges() ) {
+ if ( have_write_perm )
+ m_htmlPart->saveLayout( active_filename );
+ else {
+ switch ( KMessageBox::warningYesNo( this, i18n( "Unable to save the layout because you do not have sufficient permissions to modify this file.\nWould you like to instead save the current layout to a new file?" ) ) ) {
+ case KMessageBox::Yes:
+ saveAsLayout();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void PageSetupDialog::saveAsLayout()
+{
+ QString filename = KFileDialog::getSaveFileName( KGlobal::instance() ->dirs() ->saveLocation( "appdata", "layouts/" ), "*.klo|Krecipes Layout (*.klo)", this, QString::null );
+
+ if ( !filename.isEmpty() ) {
+ if ( haveWritePerm( filename ) ) {
+ m_htmlPart->saveLayout( filename );
+ setActiveFile(filename);
+ }
+ else {
+ switch ( KMessageBox::warningYesNo( this, i18n( "You have selected a file that you do not have the permissions to write to.\nWould you like to select another file?" ) ) ) {
+ case KMessageBox::Yes:
+ saveAsLayout();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+QString PageSetupDialog::getIncludedLayoutDir() const
+{
+ QFileInfo file_info( locate( "appdata", "layouts/Default.klo" ) );
+ return file_info.dirPath( true );
+}
+
+void PageSetupDialog::setActiveFile( const QString &filename )
+{
+ active_filename = filename;
+ have_write_perm = haveWritePerm( filename );
+}
+
+bool PageSetupDialog::haveWritePerm( const QString &filename )
+{
+ QFileInfo info( filename );
+
+ if ( info.exists() ) //check that we can write to this particular file
+ {
+ QFileInfo info( filename );
+ return info.isWritable();
+ }
+ else //check that we can write to the directory since the file doesn't exist
+ {
+ QFileInfo dir_info( info.dirPath( true ) );
+ return dir_info.isWritable();
+ }
+}
+
+
+#include "pagesetupdialog.moc"
diff --git a/krecipes/src/dialogs/pagesetupdialog.h b/krecipes/src/dialogs/pagesetupdialog.h
new file mode 100644
index 0000000..50d6f3e
--- /dev/null
+++ b/krecipes/src/dialogs/pagesetupdialog.h
@@ -0,0 +1,72 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PAGESETUPDIALOG_H
+#define PAGESETUPDIALOG_H
+
+#include <qmap.h>
+
+#include <kdialog.h>
+
+#include "datablocks/recipe.h"
+
+class KPopupMenu;
+
+class SetupDisplay;
+class KreDisplayItem;
+
+/**
+ * @author Jason Kivlighn
+ */
+class PageSetupDialog : public KDialog
+{
+ Q_OBJECT
+
+public:
+ PageSetupDialog( QWidget *parent, const Recipe &sample, const QString &configEntry = QString::null );
+ ~PageSetupDialog()
+ {}
+
+protected:
+ virtual void accept();
+ virtual void reject();
+ void save();
+
+private slots:
+ void loadFile();
+ void loadLayout( int );
+ void loadLayout( const QString &filename );
+ void loadTemplate( int );
+ void reloadLayout();
+ void saveLayout();
+ void saveAsLayout();
+ void updateItemVisibility( KreDisplayItem*, bool );
+ void setItemShown( int id );
+
+private:
+ QString getIncludedLayoutDir() const;
+ void setActiveFile( const QString &filename );
+ bool haveWritePerm( const QString &filename );
+ void initShownItems();
+
+ SetupDisplay *m_htmlPart;
+
+ QString active_filename;
+ QString active_template;
+ bool have_write_perm;
+ QString m_configEntry;
+
+ QMap<int, KreDisplayItem*> popup_widget_map;
+ QMap<KreDisplayItem*, int> widget_popup_map;
+ QMap<int, QString> included_layouts_map;
+ KPopupMenu *shown_items_popup;
+};
+
+#endif //PAGESETUPDIALOG_H
diff --git a/krecipes/src/dialogs/prepmethodsdialog.cpp b/krecipes/src/dialogs/prepmethodsdialog.cpp
new file mode 100644
index 0000000..4b5d5e0
--- /dev/null
+++ b/krecipes/src/dialogs/prepmethodsdialog.cpp
@@ -0,0 +1,67 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "prepmethodsdialog.h"
+#include "createelementdialog.h"
+#include "dependanciesdialog.h"
+#include "backends/recipedb.h"
+#include "widgets/prepmethodlistview.h"
+
+#include <kdialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+PrepMethodsDialog::PrepMethodsDialog( QWidget* parent, RecipeDB *db ) : QWidget( parent )
+{
+
+ // Store pointer to database
+ database = db;
+
+ QHBoxLayout* layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ //PrepMethod List
+ prepMethodListView = new StdPrepMethodListView( this, database, true );
+ layout->addWidget( prepMethodListView );
+
+ //Buttons
+ QVBoxLayout* vboxl = new QVBoxLayout( KDialog::spacingHint() );
+
+ newPrepMethodButton = new QPushButton( this );
+ newPrepMethodButton->setText( i18n( "Create ..." ) );
+ newPrepMethodButton->setFlat( true );
+ vboxl->addWidget( newPrepMethodButton );
+
+ removePrepMethodButton = new QPushButton( this );
+ removePrepMethodButton->setText( i18n( "Delete" ) );
+ removePrepMethodButton->setFlat( true );
+ vboxl->addWidget( removePrepMethodButton );
+ vboxl->addStretch();
+
+ layout->addLayout( vboxl );
+
+ //Connect Signals & Slots
+
+ connect ( newPrepMethodButton, SIGNAL( clicked() ), prepMethodListView, SLOT( createNew() ) );
+ connect ( removePrepMethodButton, SIGNAL( clicked() ), prepMethodListView, SLOT( remove
+ () ) );
+}
+
+PrepMethodsDialog::~PrepMethodsDialog()
+{}
+
+// (Re)loads the data from the database
+void PrepMethodsDialog::reload( ReloadFlags flag )
+{
+ prepMethodListView->reload( flag );
+}
+
+#include "prepmethodsdialog.moc"
diff --git a/krecipes/src/dialogs/prepmethodsdialog.h b/krecipes/src/dialogs/prepmethodsdialog.h
new file mode 100644
index 0000000..d24c5c1
--- /dev/null
+++ b/krecipes/src/dialogs/prepmethodsdialog.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PREPMETHODSDIALOG_H
+#define PREPMETHODSDIALOG_H
+
+#include <qwidget.h>
+#include <qpushbutton.h>
+#include <qhbox.h>
+#include <qlayout.h>
+#include <kiconloader.h>
+#include <klistview.h>
+
+#include "widgets/dblistviewbase.h"
+
+class RecipeDB;
+class StdPrepMethodListView;
+
+/**
+@author Unai Garro
+*/
+
+class PrepMethodsDialog: public QWidget
+{
+
+ Q_OBJECT
+
+public:
+
+ PrepMethodsDialog( QWidget* parent, RecipeDB *db );
+ ~PrepMethodsDialog();
+ void reload( ReloadFlags flag = Load );
+private:
+ // Internal data
+ RecipeDB *database;
+ //Widgets
+ QGridLayout *layout;
+ StdPrepMethodListView *prepMethodListView;
+ QHBox *buttonBar;
+ QPushButton *newPrepMethodButton;
+ QPushButton *removePrepMethodButton;
+ KIconLoader *il;
+};
+#endif
diff --git a/krecipes/src/dialogs/propertiesdialog.cpp b/krecipes/src/dialogs/propertiesdialog.cpp
new file mode 100644
index 0000000..02ed6d9
--- /dev/null
+++ b/krecipes/src/dialogs/propertiesdialog.cpp
@@ -0,0 +1,77 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "propertiesdialog.h"
+#include <klocale.h>
+#include <kdialog.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+
+#include "backends/recipedb.h"
+#include "createpropertydialog.h"
+#include "widgets/propertylistview.h"
+
+PropertiesDialog::PropertiesDialog( QWidget *parent, RecipeDB *db ) : QWidget( parent )
+{
+
+ // Store pointer to database
+ database = db;
+
+ // Design dialog
+
+ QHBoxLayout* layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ propertyListView = new CheckPropertyListView( this, database, true );
+ propertyListView->reload();
+
+ KConfig *config = KGlobal::config();
+ config->setGroup("Formatting");
+ QStringList hiddenList = config->readListEntry("HiddenProperties");
+ for ( QCheckListItem *item = (QCheckListItem*)propertyListView->firstChild(); item; item = (QCheckListItem*)item->nextSibling() ) {
+ if ( !hiddenList.contains(item->text(0)) )
+ item->setOn(true);
+ }
+
+ layout->addWidget ( propertyListView );
+
+ QVBoxLayout* vboxl = new QVBoxLayout( KDialog::spacingHint() );
+ addPropertyButton = new QPushButton( this );
+ addPropertyButton->setText( i18n( "Create ..." ) );
+ addPropertyButton->setFlat( true );
+ vboxl->addWidget( addPropertyButton );
+ removePropertyButton = new QPushButton( this );
+ removePropertyButton->setText( i18n( "Delete" ) );
+ removePropertyButton->setFlat( true );
+ vboxl->addWidget( removePropertyButton );
+ vboxl->addStretch();
+ layout->addLayout( vboxl );
+
+ // Connect signals & slots
+ connect( addPropertyButton, SIGNAL( clicked() ), propertyListView, SLOT( createNew() ) );
+ connect( removePropertyButton, SIGNAL( clicked() ), propertyListView, SLOT( remove
+ () ) );
+
+ //FIXME: We've got some sort of build issue... we get undefined references to CreatePropertyDialog without this dummy code here
+ UnitList list;
+ CreatePropertyDialog d( this, &list );
+}
+
+
+PropertiesDialog::~PropertiesDialog()
+{}
+
+void PropertiesDialog::reload( void )
+{
+ propertyListView->reload();
+}
+
+#include "propertiesdialog.moc"
diff --git a/krecipes/src/dialogs/propertiesdialog.h b/krecipes/src/dialogs/propertiesdialog.h
new file mode 100644
index 0000000..d197154
--- /dev/null
+++ b/krecipes/src/dialogs/propertiesdialog.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PROPERTIESDIALOG_H
+#define PROPERTIESDIALOG_H
+
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qvbox.h>
+#include <klistview.h>
+
+class RecipeDB;
+class StdPropertyListView;
+
+/**
+@author Unai Garro
+*/
+class PropertiesDialog: public QWidget
+{
+ Q_OBJECT
+public:
+ PropertiesDialog( QWidget *parent, RecipeDB *db );
+ ~PropertiesDialog();
+ void reload( void );
+
+private:
+ // Variables
+ RecipeDB *database;
+
+ // Widgets
+ QGridLayout* layout;
+ QPushButton* addPropertyButton;
+ QPushButton* removePropertyButton;
+ StdPropertyListView* propertyListView;
+
+};
+
+#endif
diff --git a/krecipes/src/dialogs/recipeimportdialog.cpp b/krecipes/src/dialogs/recipeimportdialog.cpp
new file mode 100644
index 0000000..a57ba69
--- /dev/null
+++ b/krecipes/src/dialogs/recipeimportdialog.cpp
@@ -0,0 +1,185 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipeimportdialog.h"
+
+#include <klocale.h>
+#include <kpushbutton.h>
+#include <klistview.h>
+#include <kdebug.h>
+
+#include <qvbox.h>
+#include <qlayout.h>
+#include <qheader.h>
+#include <qvariant.h>
+#include <qdict.h>
+
+#include "datablocks/recipe.h"
+
+RecipeImportDialog::RecipeImportDialog( const RecipeList &list, QWidget *parent )
+ : KDialogBase( parent, "RecipeImportDialog", true, i18n( "Import Recipes" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ),
+ list_copy( list )
+{
+ setButtonBoxOrientation( Vertical );
+
+ QVBox *page = makeVBoxMainWidget();
+
+ kListView = new KListView( page );
+ kListView->addColumn( i18n( "Recipes" ) );
+ kListView->setProperty( "selectionMode", "NoSelection" );
+ kListView->setRootIsDecorated( true );
+ kListView->setAllColumnsShowFocus( true );
+
+ languageChange();
+
+ setInitialSize( QSize( 600, 480 ).expandedTo( minimumSizeHint() ) );
+
+ loadListView();
+}
+
+RecipeImportDialog::~RecipeImportDialog()
+{
+ delete recipe_items;
+}
+
+void RecipeImportDialog::languageChange()
+{
+}
+
+void RecipeImportDialog::loadListView()
+{
+ CustomCheckListItem * head_item = new CustomCheckListItem( kListView, QString( i18n( "All (%1)" ) ).arg( list_copy.count() ), QCheckListItem::CheckBox );
+ head_item->setOpen( true );
+
+ //get all categories
+ QStringList categoryList;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = list_copy.begin(); recipe_it != list_copy.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ if ( categoryList.contains( ( *cat_it ).name ) < 1 )
+ categoryList << ( *cat_it ).name;
+ }
+ }
+
+ //create all category check list items
+ QDict<CustomCheckListItem> all_categories;
+
+ QStringList::iterator it;
+ for ( it = categoryList.begin(); it != categoryList.end(); ++it ) {
+ CustomCheckListItem *category_item = new CustomCheckListItem( head_item, *it, QCheckListItem::CheckBox );
+ //category_item->setOpen(true);
+
+ all_categories.insert( *it, category_item );
+ }
+
+ //add recipes to category check list items
+ recipe_items = new QMap<CustomCheckListItem*, RecipeList::const_iterator>; //we won't be able to identify a recipe later if we just put a value in here. The iterator will be unique so we'll use it. This is safe since the list is constant (iterators won't become invlalid).
+
+ CustomCheckListItem *item = 0;
+ CustomCheckListItem *category_item = 0;
+
+ for ( recipe_it = list_copy.begin(); recipe_it != list_copy.end(); ++recipe_it ) {
+ if ( ( *recipe_it ).categoryList.count() == 0 ) {
+ if ( !category_item ) //don't create this until there are recipes to put in it
+ {
+ category_item = new CustomCheckListItem( head_item, i18n( "Uncategorized" ), QCheckListItem::CheckBox );
+ all_categories.insert( i18n( "Uncategorized" ), category_item );
+ }
+ CustomCheckListItem *item = new CustomCheckListItem( category_item, ( *recipe_it ).title, QCheckListItem::CheckBox );
+ recipe_items->insert( item, recipe_it );
+ }
+ else {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+
+ CustomCheckListItem *category_item = all_categories[ ( *cat_it ).name ];
+
+ item = new CustomCheckListItem( category_item, item, ( *recipe_it ).title, QCheckListItem::CheckBox );
+ recipe_items->insert( item, recipe_it );
+ }
+ }
+ }
+
+ //append the number of recipes in each category to the check list item text
+ QDictIterator<CustomCheckListItem> categories_it( all_categories );
+ for ( ; categories_it.current(); ++categories_it ) {
+ int count = 0;
+ for ( QCheckListItem * it = static_cast<QCheckListItem*>( categories_it.current() ->firstChild() ); it; it = static_cast<QCheckListItem*>( it->nextSibling() ) ) {
+ count++;
+ }
+ categories_it.current() ->setText( 0, categories_it.current() ->text( 0 ) + QString( " (%1)" ).arg( count ) );
+ }
+
+ head_item->setOn( true ); //this will check all recipes
+}
+
+RecipeList RecipeImportDialog::getSelectedRecipes()
+{
+ RecipeList selected_recipes;
+
+ QValueList<RecipeList::const_iterator> already_included_recipes;
+
+ QMap<CustomCheckListItem*, RecipeList::const_iterator>::const_iterator it;
+ for ( it = recipe_items->begin(); it != recipe_items->end(); ++it ) {
+ if ( static_cast<CustomCheckListItem*>( it.key() ) ->isOn() &&
+ ( already_included_recipes.contains( it.data() ) == 0 ) ) //make sure it isn't already in the list
+ {
+ already_included_recipes.prepend( it.data() );
+ selected_recipes.prepend( *it.data() );
+ }
+ }
+
+ return selected_recipes;
+}
+
+CustomCheckListItem::CustomCheckListItem( QListView *parent, const QString & s, Type t )
+ : QCheckListItem( parent, s, t ), m_locked( false )
+{}
+
+CustomCheckListItem::CustomCheckListItem( CustomCheckListItem *parent, const QString & s, Type t )
+ : QCheckListItem( parent, s, t ), m_locked( false )
+{}
+
+CustomCheckListItem::CustomCheckListItem( QCheckListItem *parent, QCheckListItem *after, const QString & s, Type t )
+ : QCheckListItem( parent, after, s, t ), m_locked( false )
+{}
+
+void CustomCheckListItem::stateChange( bool on )
+{
+ if ( !m_locked ) {
+ for ( QCheckListItem * it = static_cast<QCheckListItem*>( firstChild() ); it; it = static_cast<QCheckListItem*>( it->nextSibling() ) ) {
+ it->setOn( on );
+ }
+ }
+
+ if ( !on ) {
+ QListViewItem * parent = this->parent();
+ if ( parent && ( parent->rtti() == 1 ) ) {
+ CustomCheckListItem * item = static_cast<CustomCheckListItem*>( parent );
+ item->setLocked( true );
+ item->setOn( on );
+ item->setLocked( false );
+ }
+ }
+
+ QString thisText = text(0);
+ QListViewItemIterator it( listView() );
+ while ( it.current() ) {
+ if ( it.current()->rtti() == 1 && it.current()->text(0) == thisText ) {
+ CustomCheckListItem * item = static_cast<CustomCheckListItem*>( it.current() );
+ item->setOn( on );
+ }
+ ++it;
+ }
+}
+
diff --git a/krecipes/src/dialogs/recipeimportdialog.h b/krecipes/src/dialogs/recipeimportdialog.h
new file mode 100644
index 0000000..d41c1b4
--- /dev/null
+++ b/krecipes/src/dialogs/recipeimportdialog.h
@@ -0,0 +1,80 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEIMPORTDIALOG_H
+#define RECIPEIMPORTDIALOG_H
+
+#include <qvaluelist.h>
+#include <qmap.h>
+#include <qlistview.h>
+
+#include <kdialogbase.h>
+
+#include "datablocks/recipelist.h"
+
+class KListView;
+
+class QListViewItem;
+
+class Recipe;
+class CustomCheckListItem;
+
+/**
+ * @author Jason Kivlighn
+ */
+class RecipeImportDialog : public KDialogBase
+{
+public:
+ RecipeImportDialog( const RecipeList &all_recipes, QWidget *parent = 0 );
+ ~RecipeImportDialog();
+
+ RecipeList getSelectedRecipes();
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ void loadListView();
+
+ KListView* kListView;
+
+ QMap<CustomCheckListItem*, RecipeList::const_iterator> *recipe_items;
+ const RecipeList list_copy;
+};
+
+/** A specialized QCheckListItem that sets the state of its children to its
+ * current state.
+ * @author Jason Kivlighn
+ */
+class CustomCheckListItem : public QCheckListItem
+{
+public:
+ CustomCheckListItem( QListView *parent, const QString &, Type );
+ CustomCheckListItem( CustomCheckListItem *parent, const QString &, Type );
+ CustomCheckListItem( QCheckListItem *parent, QCheckListItem *after, const QString &, Type );
+
+protected:
+ virtual void stateChange( bool );
+ bool locked() const
+ {
+ return m_locked;
+ }
+ void setLocked( bool b )
+ {
+ m_locked = b;
+ }
+
+private:
+ bool m_locked;
+};
+
+#endif //RECIPEIMPORTDIALOG_H
diff --git a/krecipes/src/dialogs/recipeinputdialog.cpp b/krecipes/src/dialogs/recipeinputdialog.cpp
new file mode 100644
index 0000000..7595a53
--- /dev/null
+++ b/krecipes/src/dialogs/recipeinputdialog.cpp
@@ -0,0 +1,1641 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipeinputdialog.h"
+
+#include <qstring.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qimage.h>
+#include <qmessagebox.h>
+#include <qtooltip.h>
+#include <qdatetimeedit.h>
+#include <qdragobject.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qwidgetstack.h>
+#include <qpainter.h>
+
+#include <kapplication.h>
+#include <kcompletionbox.h>
+#include <kspell.h>
+#include <kurl.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kled.h>
+#include <kdialogbase.h>
+
+#include "selectauthorsdialog.h"
+#include "resizerecipedialog.h"
+#include "ingredientparserdialog.h"
+#include "editratingdialog.h"
+#include "createunitdialog.h"
+#include "datablocks/recipe.h"
+#include "datablocks/categorytree.h"
+#include "datablocks/unit.h"
+#include "datablocks/weight.h"
+#include "backends/recipedb.h"
+#include "selectcategoriesdialog.h"
+#include "widgets/fractioninput.h"
+#include "widgets/kretextedit.h"
+#include "widgets/inglistviewitem.h"
+#include "../widgets/ratingdisplaywidget.h"
+#include "widgets/kwidgetlistbox.h"
+#include "widgets/ingredientinputwidget.h"
+#include "image.h" //Initializes default photo
+
+#include "profiling.h"
+
+typedef enum ColorStatus { GreenStatus, RedStatus, YellowStatus };
+
+ClickableLed::ClickableLed( QWidget *parent ) : KLed(parent)
+{
+}
+
+void ClickableLed::mouseReleaseEvent( QMouseEvent* )
+{
+ emit clicked();
+}
+
+ImageDropLabel::ImageDropLabel( QWidget *parent, QPixmap &_sourcePhoto ) : QLabel( parent ),
+ sourcePhoto( _sourcePhoto )
+{
+ setAcceptDrops( TRUE );
+}
+
+void ImageDropLabel::dragEnterEvent( QDragEnterEvent* event )
+{
+ event->accept( QImageDrag::canDecode( event ) );
+}
+
+void ImageDropLabel::dropEvent( QDropEvent* event )
+{
+ QImage image;
+
+ if ( QImageDrag::decode( event, image ) ) {
+ if ( ( image.width() > width() || image.height() > height() ) || ( image.width() < width() && image.height() < height() ) ) {
+ QPixmap pm_scaled;
+ pm_scaled.convertFromImage( image.smoothScale( width(), height(), QImage::ScaleMin ) );
+ setPixmap( pm_scaled );
+
+ sourcePhoto = pm_scaled; // to save scaled later on
+ }
+ else {
+ setPixmap( image );
+ sourcePhoto = image;
+ }
+
+ emit changed();
+ }
+}
+
+
+RecipeInputDialog::RecipeInputDialog( QWidget* parent, RecipeDB *db ) : QVBox( parent )
+{
+
+ // Adjust internal parameters
+ loadedRecipe = new Recipe();
+ loadedRecipe->recipeID = -1; // No loaded recipe initially
+ loadedRecipe->title = QString::null;
+ loadedRecipe->instructions = QString::null;
+ database = db;
+
+ KIconLoader *il = new KIconLoader;
+
+ // Tabs
+ tabWidget = new QTabWidget( this, "tabWidget" );
+ tabWidget->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+
+ //------- Recipe Tab -----------------
+ // Recipe Photo
+
+ recipeTab = new QGroupBox( tabWidget );
+ recipeTab->setFrameStyle( QFrame::NoFrame );
+ recipeTab->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+
+ // Design the Dialog
+ QGridLayout* recipeLayout = new QGridLayout( recipeTab, 1, 1, 0, 0 );
+
+ // Border
+ QSpacerItem* spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ recipeLayout->addItem( spacer_left, 1, 0 );
+ QSpacerItem* spacer_right = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ recipeLayout->addItem( spacer_right, 1, 8 );
+ QSpacerItem* spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum , QSizePolicy::Fixed );
+ recipeLayout->addItem( spacer_top, 0, 1 );
+ QSpacerItem* spacer_bottom = new QSpacerItem( 10, 10, QSizePolicy::Minimum , QSizePolicy::MinimumExpanding );
+ recipeLayout->addItem( spacer_bottom, 8, 1 );
+
+
+ QPixmap image1( defaultPhoto );
+
+ photoLabel = new ImageDropLabel( recipeTab, sourcePhoto );
+ photoLabel->setPixmap( image1 );
+ photoLabel->setFixedSize( QSize( 221, 166 ) );
+ photoLabel->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ photoLabel->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter );
+ recipeLayout->addMultiCellWidget( photoLabel, 3, 7, 1, 1 );
+
+ QVBox *photoButtonsBox = new QVBox( recipeTab );
+
+ changePhotoButton = new QPushButton( photoButtonsBox );
+ changePhotoButton->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ) );
+ changePhotoButton->setText( "..." );
+ QToolTip::add
+ ( changePhotoButton, i18n( "Select photo" ) );
+
+ QPushButton *clearPhotoButton = new QPushButton( photoButtonsBox );
+ clearPhotoButton->setPixmap( il->loadIcon( "clear_left", KIcon::NoGroup, 16 ) );
+ QToolTip::add
+ ( clearPhotoButton, i18n( "Clear photo" ) );
+
+ recipeLayout->addMultiCellWidget( photoButtonsBox, 3, 7, 2, 2 );
+
+
+ //Title->photo spacer
+ QSpacerItem* title_photo = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ recipeLayout->addItem( title_photo, 2, 3 );
+
+
+ // Title
+ QVBox *titleBox = new QVBox( recipeTab );
+ titleBox->setSpacing( 5 );
+ titleLabel = new QLabel( i18n( "Recipe Name" ), titleBox );
+ titleEdit = new KLineEdit( titleBox );
+ titleEdit->setMinimumSize( QSize( 360, 30 ) );
+ titleEdit->setMaximumSize( QSize( 10000, 30 ) );
+ titleEdit->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+ recipeLayout->addMultiCellWidget( titleBox, 1, 1, 1, 7 );
+
+
+ // Photo ->author spacer
+ QSpacerItem* title_spacer = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ recipeLayout->addItem( title_spacer, 2, 1 );
+
+ // Author(s) & Categories
+ QVBox *authorBox = new QVBox( recipeTab ); // contains label and authorInput (input widgets)
+ authorBox->setSpacing( 5 );
+ recipeLayout->addWidget( authorBox, 3, 4 );
+ authorLabel = new QLabel( i18n( "Authors" ), authorBox );
+ QHBox *authorInput = new QHBox( authorBox ); // Contains input + button
+
+
+ authorShow = new KLineEdit( authorInput );
+ authorShow->setReadOnly( true );
+ authorShow->setMinimumSize( QSize( 100, 20 ) );
+ authorShow->setMaximumSize( QSize( 10000, 20 ) );
+ authorShow->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+
+
+ addAuthorButton = new QPushButton( authorInput );
+ addAuthorButton->setText( "+" );
+ addAuthorButton->setFixedSize( QSize( 20, 20 ) );
+ addAuthorButton->setFlat( true );
+
+
+ QSpacerItem* author_category = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ recipeLayout->addItem( author_category, 3, 5 );
+
+ QVBox *categoryBox = new QVBox( recipeTab ); // Contains the label and categoryInput (input widgets)
+ categoryBox->setSpacing( 5 );
+ categoryLabel = new QLabel( i18n( "Categories" ), categoryBox );
+ QHBox *categoryInput = new QHBox( categoryBox ); // Contains the input widgets
+
+ categoryShow = new KLineEdit( categoryInput );
+ categoryShow->setReadOnly( true );
+ categoryShow->setMinimumSize( QSize( 100, 20 ) );
+ categoryShow->setMaximumSize( QSize( 10000, 20 ) );
+ categoryShow->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+ recipeLayout->addWidget( categoryBox, 4, 4 );
+
+ addCategoryButton = new QPushButton( categoryInput );
+ addCategoryButton->setText( "+" );
+ addCategoryButton->setFixedSize( QSize( 20, 20 ) );
+ addCategoryButton->setFlat( true );
+
+ //Category ->Servings spacer
+ QSpacerItem* category_yield = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ recipeLayout->addItem( category_yield, 5, 4 );
+
+ QHBox *serv_prep_box = new QHBox( recipeTab );
+ serv_prep_box->setSpacing( 5 );
+
+ // Backup options
+ QGroupBox *yieldGBox = new QGroupBox( serv_prep_box, "yieldGBox" );
+ yieldGBox->setTitle( i18n( "Yield" ) );
+ yieldGBox->setColumns( 2 );
+
+ yieldLabel = new QLabel( i18n( "Amount" ), yieldGBox );
+ /*QLabel *yieldTypeLabel = */new QLabel( i18n( "Type" ), yieldGBox );
+ yieldNumInput = new FractionInput( yieldGBox );
+ yieldNumInput->setAllowRange(true);
+ yieldTypeEdit = new KLineEdit( yieldGBox );
+
+ QVBox *prepTimeBox = new QVBox( serv_prep_box );
+ prepTimeBox->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ) );
+ prepTimeBox->setSpacing( 5 );
+
+ ( void ) new QLabel( i18n( "Preparation Time" ), prepTimeBox );
+ prepTimeEdit = new QTimeEdit( prepTimeBox );
+ prepTimeEdit->setMinValue( QTime( 0, 0 ) );
+ prepTimeEdit->setDisplay( QTimeEdit::Hours | QTimeEdit::Minutes );
+
+ recipeLayout->addWidget( serv_prep_box, 6, 4 );
+
+ //------- END OF Recipe Tab ---------------
+
+ //------- Ingredients Tab -----------------
+
+ ingredientGBox = new QGroupBox( recipeTab );
+ ingredientGBox->setFrameStyle( QFrame::NoFrame );
+ ingredientGBox->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+ QGridLayout* ingredientsLayout = new QGridLayout( ingredientGBox );
+
+ // Border
+ QSpacerItem* spacerBoxLeft = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ ingredientsLayout->addItem( spacerBoxLeft, 1, 0 );
+ QSpacerItem* spacerBoxTop = new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ ingredientsLayout->addItem( spacerBoxTop, 0, 1 );
+
+ //Input Widgets
+ ingInput = new IngredientInputWidget( database, ingredientGBox );
+ ingredientsLayout->addMultiCellWidget( ingInput, 1, 1, 1, 5 );
+
+ // Spacers to list and buttons
+ QSpacerItem* spacerToList = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ ingredientsLayout->addItem( spacerToList, 2, 1 );
+ QSpacerItem* spacerToButtons = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ ingredientsLayout->addItem( spacerToButtons, 3, 4 );
+
+ // Add, Up,down,... buttons
+
+ addButton = new KPushButton( ingredientGBox );
+ addButton->setFixedSize( QSize( 31, 31 ) );
+ addButton->setFlat( true );
+ QPixmap pm = il->loadIcon( "new", KIcon::NoGroup, 16 );
+ addButton->setPixmap( pm );
+ addButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ ingredientsLayout->addWidget( addButton, 3, 5 );
+
+ // Spacer to the rest of buttons
+ QSpacerItem* spacerToOtherButtons = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ ingredientsLayout->addItem( spacerToOtherButtons, 4, 5 );
+
+ upButton = new KPushButton( ingredientGBox );
+ upButton->setFixedSize( QSize( 31, 31 ) );
+ upButton->setFlat( true );
+ pm = il->loadIcon( "up", KIcon::NoGroup, 16 );
+ upButton->setPixmap( pm );
+ upButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ ingredientsLayout->addWidget( upButton, 5, 5 );
+
+ downButton = new KPushButton( ingredientGBox );
+ downButton->setFixedSize( QSize( 31, 31 ) );
+ downButton->setFlat( true );
+ pm = il->loadIcon( "down", KIcon::NoGroup, 16 );
+ downButton->setPixmap( pm );
+ downButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ ingredientsLayout->addWidget( downButton, 6, 5 );
+
+ removeButton = new KPushButton( ingredientGBox );
+ removeButton->setFixedSize( QSize( 31, 31 ) );
+ removeButton->setFlat( true );
+ pm = il->loadIcon( "remove", KIcon::NoGroup, 16 );
+ removeButton->setPixmap( pm );
+ removeButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ ingredientsLayout->addWidget( removeButton, 7, 5 );
+
+ ingParserButton = new KPushButton( ingredientGBox );
+ ingParserButton->setFixedSize( QSize( 31, 31 ) );
+ ingParserButton->setFlat( true );
+ pm = il->loadIcon( "editpaste", KIcon::NoGroup, 16 );
+ ingParserButton->setPixmap( pm );
+ ingParserButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ ingredientsLayout->addWidget( ingParserButton, 8, 5 );
+
+ QToolTip::add
+ ( addButton, i18n( "Add ingredient" ) );
+ QToolTip::add
+ ( upButton, i18n( "Move ingredient up" ) );
+ QToolTip::add
+ ( downButton, i18n( "Move ingredient down" ) );
+ QToolTip::add
+ ( removeButton, i18n( "Remove ingredient" ) );
+ QToolTip::add
+ ( ingParserButton, i18n( "Paste Ingredients" ) );
+
+ // Ingredient List
+ ingredientList = new KListView( ingredientGBox, "ingredientList" );
+ ingredientList->addColumn( i18n( "Ingredient" ) );
+ ingredientList->addColumn( i18n( "Amount" ) );
+ ingredientList->setColumnAlignment( 1, Qt::AlignHCenter );
+ ingredientList->addColumn( i18n( "Units" ) );
+ ingredientList->addColumn( i18n( "Preparation Method" ) );
+ ingredientList->setSorting( -1 ); // Do not sort
+ ingredientList->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::MinimumExpanding ) );
+ ingredientList->setItemsRenameable( true );
+ ingredientList->setRenameable( 0, false ); //name
+ ingredientList->setRenameable( 1, true ); //amount
+ ingredientList->setRenameable( 2, true ); //units
+ ingredientList->setRenameable( 3, true ); //prep method
+ ingredientList->setDefaultRenameAction( QListView::Reject );
+ ingredientsLayout->addMultiCellWidget( ingredientList, 3, 9, 1, 3 );
+
+ QHBoxLayout *propertyStatusLayout = new QHBoxLayout( ingredientGBox, 0, 5 );
+ QLabel *propertyLabel = new QLabel( i18n("Property Status:"), ingredientGBox );
+ propertyStatusLabel = new QLabel( ingredientGBox );
+ propertyStatusLed = new ClickableLed( ingredientGBox );
+ propertyStatusLed->setFixedSize( QSize(16,16) );
+ propertyStatusButton = new QPushButton( i18n("Details..."), ingredientGBox );
+ //QPushButton *propertyUpdateButton = new QPushButton( i18n("Update"), ingredientGBox );
+ propertyStatusLayout->addWidget( propertyLabel );
+ propertyStatusLayout->addWidget( propertyStatusLabel );
+ propertyStatusLayout->addWidget( propertyStatusLed );
+ propertyStatusLayout->addWidget( propertyStatusButton );
+ //propertyStatusLayout->addWidget( propertyUpdateButton );
+ QSpacerItem* propertySpacerRight = new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ propertyStatusLayout->addItem( propertySpacerRight );
+
+ KGuiItem updateGuiItem;
+ updateGuiItem.setText( i18n("Update") );
+ updateGuiItem.setIconSet( il->loadIconSet( "reload", KIcon::NoGroup ) );
+ propertyStatusDialog = new KDialogBase( KDialogBase::Swallow, i18n("Property details"),
+ KDialogBase::Close | KDialogBase::User1 | KDialogBase::Help,
+ KDialogBase::Close, this, "propertyStatusDialog", false, false,
+ updateGuiItem
+ );
+ propertyStatusDialog->setHelp("property-status");
+ statusTextView = new QTextEdit(0);
+ statusTextView->setTextFormat( Qt::RichText );
+ statusTextView->setReadOnly(true);
+ propertyStatusDialog->setMainWidget( statusTextView );
+ propertyStatusDialog->resize( 400, 300 );
+
+ ingredientsLayout->addMultiCellLayout( propertyStatusLayout, 10, 10, 1, 4 );
+
+ // ------- Recipe Instructions Tab -----------
+
+ instructionsTab = new QGroupBox( recipeTab );
+ instructionsTab->setFrameStyle( QFrame::NoFrame );
+ instructionsTab->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+ QVBoxLayout *instructionsLayout = new QVBoxLayout( instructionsTab );
+
+ instructionsEdit = new KreTextEdit( instructionsTab );
+ instructionsEdit->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+ instructionsEdit->setTabChangesFocus ( true );
+ instructionsLayout->addWidget( instructionsEdit );
+
+ spellCheckButton = new QToolButton( instructionsTab );
+ spellCheckButton->setIconSet( il->loadIconSet( "spellcheck", KIcon::Small ) );
+ QToolTip::add
+ ( spellCheckButton, i18n( "Check spelling" ) );
+ instructionsLayout->addWidget( spellCheckButton );
+
+ // ------- END OF Recipe Instructions Tab -----------
+
+
+ // ------- Recipe Ratings Tab -----------
+
+ QVBox *ratingsTab = new QVBox(recipeTab);
+ ratingListDisplayWidget = new KWidgetListbox(ratingsTab);
+ QPushButton *addRatingButton = new QPushButton(i18n("Add Rating..."),ratingsTab);
+
+ connect( addRatingButton, SIGNAL(clicked()), this, SLOT(slotAddRating()) );
+
+ // ------- END OF Recipe Ratings Tab -----------
+
+
+ tabWidget->insertTab( recipeTab, i18n( "Recipe" ) );
+ tabWidget->insertTab( ingredientGBox, i18n( "Ingredients" ) );
+ tabWidget->insertTab( instructionsTab, i18n( "Instructions" ) );
+ tabWidget->insertTab( ratingsTab, i18n( "Ratings" ) );
+
+
+ // Functions Box
+ QHBox* functionsLayout = new QHBox( this );
+
+ functionsBox = new QGroupBox( 1, Qt::Vertical, functionsLayout );
+ functionsBox->setFrameStyle( QFrame::NoFrame );
+
+ saveButton = new QToolButton( functionsBox );
+ saveButton->setIconSet( il->loadIconSet( "filesave", KIcon::Small ) );
+ saveButton->setEnabled( false );
+ showButton = new QToolButton( functionsBox );
+ showButton->setIconSet( il->loadIconSet( "viewmag", KIcon::Small ) );
+ closeButton = new QToolButton( functionsBox );
+ closeButton->setIconSet( il->loadIconSet( "fileclose", KIcon::Small ) );
+ resizeButton = new QToolButton( functionsBox );
+ resizeButton->setIconSet( il->loadIconSet( "2uparrow", KIcon::Small ) ); //TODO: give me an icon :)
+
+ saveButton->setTextLabel( i18n( "Save recipe" ), true );
+ saveButton->setUsesTextLabel( true );
+ showButton->setTextLabel( i18n( "Show recipe" ), true );
+ showButton->setUsesTextLabel( true );
+ closeButton->setTextLabel( i18n( "Close" ), true );
+ closeButton->setUsesTextLabel( true );
+ resizeButton->setTextLabel( i18n( "Resize recipe" ), true );
+ resizeButton->setUsesTextLabel( true );
+
+ functionsLayout->layout() ->addItem( new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+
+ // Dialog design
+ tabWidget->resize( size().expandedTo( minimumSizeHint() ) );
+ clearWState( WState_Polished );
+
+ // Initialize internal data
+ unsavedChanges = false; // Indicates if there's something not saved yet.
+ enableChangedSignal(); // Enables the signal "changed()"
+
+ // Connect signals & Slots
+ connect( changePhotoButton, SIGNAL( clicked() ), this, SLOT( changePhoto() ) );
+ connect( clearPhotoButton, SIGNAL( clicked() ), SLOT( clearPhoto() ) );
+ connect( upButton, SIGNAL( clicked() ), this, SLOT( moveIngredientUp() ) );
+ connect( downButton, SIGNAL( clicked() ), this, SLOT( moveIngredientDown() ) );
+ connect( removeButton, SIGNAL( clicked() ), this, SLOT( removeIngredient() ) );
+ connect( addButton, SIGNAL( clicked() ), ingInput, SLOT( addIngredient() ) );
+ connect( ingParserButton, SIGNAL( clicked() ), this, SLOT( slotIngredientParser() ) );
+ connect( photoLabel, SIGNAL( changed() ), this, SIGNAL( changed() ) );
+ connect( this, SIGNAL( changed() ), this, SLOT( recipeChanged() ) );
+ connect( yieldNumInput, SIGNAL( textChanged( const QString & ) ), this, SLOT( recipeChanged() ) );
+ connect( yieldTypeEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( recipeChanged() ) );
+ connect( prepTimeEdit, SIGNAL( valueChanged( const QTime & ) ), SLOT( recipeChanged() ) );
+ connect( titleEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( recipeChanged( const QString& ) ) );
+ connect( instructionsEdit, SIGNAL( textChanged() ), this, SLOT( recipeChanged() ) );
+ connect( addCategoryButton, SIGNAL( clicked() ), this, SLOT( addCategory() ) );
+ connect( addAuthorButton, SIGNAL( clicked() ), this, SLOT( addAuthor() ) );
+ connect( titleEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( prepTitleChanged( const QString& ) ) );
+ connect( ingredientList, SIGNAL( itemRenamed( QListViewItem*, const QString &, int ) ), SLOT( syncListView( QListViewItem*, const QString &, int ) ) );
+
+ connect ( ingInput, SIGNAL( ingredientEntered(const Ingredient&) ), this, SLOT( addIngredient(const Ingredient&) ) );
+ connect ( ingInput, SIGNAL( headerEntered(const Element&) ), this, SLOT( addIngredientHeader(const Element&) ) );
+
+ connect( propertyStatusLed, SIGNAL(clicked()), SLOT(updatePropertyStatus()) );
+ connect( propertyStatusDialog, SIGNAL(user1Clicked()), SLOT(updatePropertyStatus()) );
+ connect( propertyStatusButton, SIGNAL(clicked()), propertyStatusDialog, SLOT(show()) );
+
+ // Function buttons
+ connect ( saveButton, SIGNAL( clicked() ), this, SLOT( save() ) );
+ connect ( closeButton, SIGNAL( clicked() ), this, SLOT( closeOptions() ) );
+ connect ( showButton, SIGNAL( clicked() ), this, SLOT( showRecipe() ) );
+ connect ( resizeButton, SIGNAL( clicked() ), this, SLOT( resizeRecipe() ) );
+ connect ( spellCheckButton, SIGNAL( clicked() ), this, SLOT( spellCheck() ) );
+ connect ( this, SIGNAL( enableSaveOption( bool ) ), this, SLOT( enableSaveButton( bool ) ) );
+
+ connect ( database, SIGNAL( recipeRemoved(int) ), this, SLOT( recipeRemoved(int) ) );
+
+ delete il;
+
+ //FIXME: We've got some sort of build issue... we get undefined references to CreateUnitDialog without this dummy code here
+ CreateUnitDialog d( this, "" );
+}
+
+
+RecipeInputDialog::~RecipeInputDialog()
+{
+ delete loadedRecipe;
+}
+
+void RecipeInputDialog::recipeRemoved( int id )
+{
+ if ( loadedRecipe->recipeID == id ) {
+ loadedRecipe->recipeID = -1;
+ recipeChanged();
+ }
+}
+
+void RecipeInputDialog::prepTitleChanged( const QString &title )
+{
+ //we don't want the menu to grow due to a long title
+ //### KStringHandler::rsqueeze does this but I can't remember when it was added (compatibility issue...)
+ QString short_title = title.left( 20 );
+ if ( title.length() > 20 )
+ short_title.append( "..." );
+
+ emit titleChanged( short_title );
+}
+
+int RecipeInputDialog::loadedRecipeID() const
+{
+ return loadedRecipe->recipeID;
+}
+
+void RecipeInputDialog::loadRecipe( int recipeID )
+{
+ emit enableSaveOption( false );
+ unsavedChanges = false;
+
+ //Disable changed() signals
+ enableChangedSignal( false );
+
+ //Empty current recipe
+ loadedRecipe->empty();
+
+ //Set back to the first page
+ tabWidget->setCurrentPage( 0 );
+
+ // Load specified Recipe ID
+ database->loadRecipe( loadedRecipe, RecipeDB::All ^ RecipeDB::Meta ^ RecipeDB::Properties, recipeID );
+
+ reload();
+
+ propertyStatusDialog->hide();
+ updatePropertyStatus();
+
+ //Enable changed() signals
+ enableChangedSignal();
+
+}
+
+void RecipeInputDialog::reload( void )
+{
+ yieldNumInput->setValue( 1, 0 );
+ yieldTypeEdit->setText("");
+ ingredientList->clear();
+ ratingListDisplayWidget->clear();
+ ingInput->clear();
+
+ //Load Values in Interface
+ titleEdit->setText( loadedRecipe->title );
+ instructionsEdit->setText( loadedRecipe->instructions );
+ yieldNumInput->setValue( loadedRecipe->yield.amount, loadedRecipe->yield.amount_offset );
+ yieldTypeEdit->setText( loadedRecipe->yield.type );
+ prepTimeEdit->setTime( loadedRecipe->prepTime );
+
+ //show ingredient list
+ IngredientList list_copy = loadedRecipe->ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ QListViewItem * lastElement = ingredientList->lastItem();
+ QListViewItem *ing_header = 0;
+
+ QString group = group_list[ 0 ].group;
+ if ( !group.isEmpty() ) {
+ if ( lastElement && lastElement->parent() )
+ lastElement = lastElement->parent();
+
+ ing_header = new IngGrpListViewItem( ingredientList, lastElement, group_list[ 0 ].group, group_list[ 0 ].groupID );
+ ing_header->setOpen( true );
+ lastElement = ing_header;
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ //Insert ingredient after last one
+ if ( ing_header ) {
+ lastElement = new IngListViewItem ( ing_header, lastElement, *ing_it );
+ }
+ else {
+ if ( lastElement && lastElement->parent() )
+ lastElement = lastElement->parent();
+ lastElement = new IngListViewItem ( ingredientList, lastElement, *ing_it );
+ }
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ new IngSubListViewItem ( lastElement, *sub_it );
+ lastElement->setOpen(true);
+ }
+
+ //update completion
+ instructionsEdit->addCompletionItem( ( *ing_it ).name );
+ }
+ }
+ //
+ //show photo
+ if ( !loadedRecipe->photo.isNull() ) {
+
+ // //get the photo
+ sourcePhoto = loadedRecipe->photo;
+
+ if ( ( sourcePhoto.width() > photoLabel->width() || sourcePhoto.height() > photoLabel->height() ) || ( sourcePhoto.width() < photoLabel->width() && sourcePhoto.height() < photoLabel->height() ) ) {
+ QImage pm = sourcePhoto.convertToImage();
+ QPixmap pm_scaled;
+ pm_scaled.convertFromImage( pm.smoothScale( photoLabel->width(), photoLabel->height(), QImage::ScaleMin ) );
+ photoLabel->setPixmap( pm_scaled );
+
+ sourcePhoto = pm_scaled; // to save scaled later on
+ }
+ else {
+ photoLabel->setPixmap( sourcePhoto );
+ }
+ }
+ else {
+ QPixmap photo = QPixmap( defaultPhoto );
+ photoLabel->setPixmap( photo );
+ sourcePhoto = QPixmap();
+ }
+
+
+ // Show categories
+ showCategories();
+
+ // Show authors
+ showAuthors();
+
+ // Show ratings
+ for ( RatingList::iterator rating_it = loadedRecipe->ratingList.begin(); rating_it != loadedRecipe->ratingList.end(); ++rating_it ) {
+ RatingDisplayWidget *item = new RatingDisplayWidget;
+ item->rating_it = rating_it;
+ addRating(*rating_it,item);
+ ratingListDisplayWidget->insertItem(item);
+ }
+ ratingListDisplayWidget->ensureCellVisible(0,0);
+
+ // Update yield type auto completion
+ KCompletion *completion = yieldTypeEdit->completionObject();
+ completion->clear();
+ ElementList yieldList;
+ database->loadYieldTypes( &yieldList );
+ for ( ElementList::const_iterator it = yieldList.begin(); it != yieldList.end(); ++it ) {
+ completion->addItem( (*it).name );
+ }
+}
+
+void RecipeInputDialog::changePhoto( void )
+{
+ // standard filedialog
+ KURL filename = KFileDialog::getOpenURL( QString::null, QString( "*.png *.jpg *.jpeg *.xpm *.gif|%1 (*.png *.jpg *.jpeg *.xpm *.gif)" ).arg( i18n( "Images" ) ), this );
+ QPixmap pixmap ( filename.path() );
+ if ( !( pixmap.isNull() ) ) {
+ // If photo is bigger than the label, or smaller in width, than photoLabel, scale it
+ sourcePhoto = pixmap;
+ if ( ( sourcePhoto.width() > photoLabel->width() || sourcePhoto.height() > photoLabel->height() ) || ( sourcePhoto.width() < photoLabel->width() && sourcePhoto.height() < photoLabel->height() ) ) {
+ QImage pm = sourcePhoto.convertToImage();
+ QPixmap pm_scaled;
+ pm_scaled.convertFromImage( pm.smoothScale( photoLabel->width(), photoLabel->height(), QImage::ScaleMin ) );
+ photoLabel->setPixmap( pm_scaled );
+
+ sourcePhoto = pm_scaled; // to save scaled later on
+ photoLabel->setPixmap( pm_scaled );
+ }
+ else {
+ photoLabel->setPixmap( sourcePhoto );
+ }
+ emit changed();
+ }
+}
+
+void RecipeInputDialog::clearPhoto( void )
+{
+ sourcePhoto = QPixmap();
+ photoLabel->setPixmap( QPixmap( defaultPhoto ) );
+
+ emit changed();
+}
+
+void RecipeInputDialog::moveIngredientUp( void )
+{
+ QListViewItem * it = ingredientList->selectedItem();
+ if ( !it || it->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ return ;
+
+ QListViewItem *iabove = it->itemAbove();
+ while ( iabove && iabove->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ iabove = iabove->itemAbove();
+
+ if ( iabove ) {
+ if ( it->rtti() == INGGRPLISTVIEWITEM_RTTI ) {
+ if ( iabove->parent() )
+ iabove = iabove->parent();
+
+ int it_index = ingItemIndex( ingredientList, it );
+ int iabove_index = ingItemIndex( ingredientList, iabove );
+
+ iabove->moveItem( it ); //Move the Item
+
+ loadedRecipe->ingList.move( iabove_index, ( iabove->rtti() == INGGRPLISTVIEWITEM_RTTI ) ? iabove->childCount() : 1, it_index + it->childCount() - 1 );
+ }
+ else {
+ int it_index = ingItemIndex( ingredientList, it );
+ int iabove_index = ingItemIndex( ingredientList, iabove );
+ IngredientList::iterator ing = loadedRecipe->ingList.at( it_index );
+
+ if ( iabove->parent() != it->parent() ) {
+ if ( iabove->rtti() == INGGRPLISTVIEWITEM_RTTI && it->parent() ) { //move the item out of the group
+ it->parent() ->takeItem( it );
+ ingredientList->insertItem( it );
+ it->moveItem( ( iabove->itemAbove() ->parent() ) ? iabove->itemAbove() ->parent() : iabove->itemAbove() ); //Move the Item
+ }
+ else { //move the item into the group
+ ingredientList->takeItem( it );
+ iabove->parent() ->insertItem( it );
+ it->moveItem( iabove ); //Move the Item
+ }
+
+ ingredientList->setCurrentItem( it ); //Keep selected
+ }
+ else {
+ iabove->moveItem( it ); //Move the Item
+ loadedRecipe->ingList.move( it_index, iabove_index );
+ }
+
+ if ( it->parent() )
+ ( *ing ).groupID = ( ( IngGrpListViewItem* ) it->parent() ) ->id();
+ else
+ ( *ing ).groupID = -1;
+ }
+
+ emit changed();
+ }
+}
+
+void RecipeInputDialog::moveIngredientDown( void )
+{
+ QListViewItem * it = ingredientList->selectedItem();
+ if ( !it || it->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ return ;
+
+ QListViewItem *ibelow = it->itemBelow();
+ while ( ibelow && ibelow->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ ibelow = ibelow->itemBelow();
+
+ if ( ibelow ) {
+ if ( it->rtti() == INGGRPLISTVIEWITEM_RTTI ) {
+ QListViewItem * next_sibling = it->nextSibling();
+
+ if ( next_sibling ) {
+ int it_index = ingItemIndex( ingredientList, it );
+ int ibelow_index = ingItemIndex( ingredientList, next_sibling );
+
+ it->moveItem( next_sibling ); //Move the Item
+
+ int skip = 0;
+ if ( next_sibling->childCount() > 0 )
+ skip = next_sibling->childCount() - 1;
+
+ loadedRecipe->ingList.move( it_index, it->childCount(), ibelow_index + skip );
+ }
+ }
+ else {
+ int it_index = ingItemIndex( ingredientList, it );
+ int ibelow_index = ingItemIndex( ingredientList, ibelow );
+ IngredientList::iterator ing = loadedRecipe->ingList.at( it_index );
+
+ if ( ibelow->rtti() == INGGRPLISTVIEWITEM_RTTI || ( ibelow->parent() != it->parent() ) ) {
+ if ( ibelow->rtti() == INGGRPLISTVIEWITEM_RTTI && !it->parent() ) { //move the item into the group
+ if ( !it->parent() )
+ ingredientList->takeItem( it );
+ else
+ it->parent() ->takeItem( it );
+
+ ibelow->insertItem( it );
+ }
+ else { //move the item out of the group
+ QListViewItem *parent = it->parent(); //store this because we can't get it after we do it->takeItem()
+ parent->takeItem( it );
+ ingredientList->insertItem( it );
+ it->moveItem( parent ); //Move the Item
+ }
+
+ ingredientList->setCurrentItem( it ); //Keep selected
+ }
+ else {
+ it->moveItem( ibelow ); //Move the Item
+ loadedRecipe->ingList.move( it_index, ibelow_index );
+ }
+
+ if ( it->parent() )
+ ( *ing ).groupID = ( ( IngGrpListViewItem* ) it->parent() ) ->id();
+ else
+ ( *ing ).groupID = -1;
+ }
+
+ emit changed();
+ }
+ else if ( it->parent() ) {
+ it->parent() ->takeItem( it );
+ ingredientList->insertItem( it );
+ it->moveItem( ( ingredientList->lastItem() ->parent() ) ? ingredientList->lastItem() ->parent() : ingredientList->lastItem() ); //Move the Item
+ ingredientList->setCurrentItem( it ); //Keep selected
+
+ int it_index = ingItemIndex( ingredientList, it );
+ IngredientList::iterator ing = loadedRecipe->ingList.at( it_index );
+ ( *ing ).groupID = -1;
+
+ emit changed();
+ }
+}
+
+void RecipeInputDialog::removeIngredient( void )
+{
+ QListViewItem * it = ingredientList->selectedItem();
+ if ( it && (it->rtti() == INGLISTVIEWITEM_RTTI || it->rtti() == INGSUBLISTVIEWITEM_RTTI) ) {
+ QListViewItem *iselect = it->itemBelow();
+ while ( iselect && iselect->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ iselect = iselect->itemBelow();
+
+ if ( !iselect ) {
+ iselect = it->itemAbove();
+ while ( iselect && iselect->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ iselect = iselect->itemAbove();
+ }
+
+ IngListViewItem *ing_item = (IngListViewItem*)it; //we can cast IngSubListViewItem to this too, it's a subclass
+
+ IngredientData &ing = loadedRecipe->ingList.findSubstitute( ing_item->ingredient() );
+
+ //Remove it from the instruction's completion
+ instructionsEdit->removeCompletionItem( ing.name );
+
+ loadedRecipe->ingList.removeSubstitute( ing );
+
+ int ingID = ing_item->ingredient().ingredientID;
+ QMap<int,QString>::iterator map_it;
+ if ( (map_it = propertyStatusMapRed.find(ingID)) != propertyStatusMapRed.end() )
+ propertyStatusMapRed.remove( map_it );
+ else if ( (map_it = propertyStatusMapYellow.find(ingID)) != propertyStatusMapYellow.end() )
+ propertyStatusMapYellow.remove( map_it );
+ showStatusIndicator();
+
+ //Now remove the ingredient
+ it->setSelected( false );
+ delete it;
+ if ( iselect )
+ ingredientList->setSelected( iselect, true ); // be careful iselect->setSelected doesn't work this way.
+
+ emit changed();
+ }
+ else if ( it && it->rtti() == INGGRPLISTVIEWITEM_RTTI ) {
+ IngGrpListViewItem * header = ( IngGrpListViewItem* ) it;
+
+ for ( IngListViewItem * sub_item = (IngListViewItem*)header->firstChild(); sub_item; sub_item = (IngListViewItem*)sub_item->nextSibling() ) {
+ IngredientData &ing = loadedRecipe->ingList.findSubstitute( sub_item->ingredient() );
+
+ //Remove it from the instruction's completion
+ instructionsEdit->removeCompletionItem( ing.name );
+
+ loadedRecipe->ingList.removeSubstitute( ing );
+
+ int ingID = sub_item->ingredient().ingredientID;
+ QMap<int,QString>::iterator map_it;
+ if ( (map_it = propertyStatusMapRed.find(ingID)) != propertyStatusMapRed.end() )
+ propertyStatusMapRed.remove( map_it );
+ else if ( (map_it = propertyStatusMapYellow.find(ingID)) != propertyStatusMapYellow.end() )
+ propertyStatusMapYellow.remove( map_it );
+ showStatusIndicator();
+ }
+
+ delete header;
+
+ emit changed();
+ }
+
+}
+
+int RecipeInputDialog::createNewYieldIfNecessary( const QString &yield )
+{
+ if ( yield.stripWhiteSpace().isEmpty() ) //no yield
+ return -1;
+ else
+ {
+ int id = database->findExistingYieldTypeByName( yield );
+ if ( id == -1 ) //creating new
+ {
+ database->createNewYieldType( yield );
+ id = database->lastInsertID();
+ }
+
+ return id;
+ }
+}
+
+void RecipeInputDialog::syncListView( QListViewItem* it, const QString &new_text, int col )
+{
+ if ( it->rtti() != INGLISTVIEWITEM_RTTI )
+ return ;
+
+ IngListViewItem *ing_item = ( IngListViewItem* ) it;
+
+ IngredientData &new_ing = loadedRecipe->ingList.findSubstitute( ing_item->ingredient() );
+
+ switch ( col ) {
+ case 1: //amount
+ {
+ bool ok;
+
+ Ingredient new_ing_amount;
+ new_ing_amount.setAmount(new_text,&ok);
+
+ if ( ok )
+ {
+ if ( new_ing.amount != new_ing_amount.amount ||
+ new_ing.amount_offset != new_ing_amount.amount_offset ) {
+ new_ing.amount = new_ing_amount.amount;
+ new_ing.amount_offset = new_ing_amount.amount_offset;
+ if ( !new_text.isEmpty() )
+ ing_item->setAmount( new_ing_amount.amount, new_ing_amount.amount_offset );
+
+ new_ing.amount = new_ing_amount.amount;
+ new_ing.amount_offset = new_ing_amount.amount_offset;
+ emit changed();
+ }
+ }
+ else
+ {
+ if ( !new_text.isEmpty() )
+ ing_item->setAmount( new_ing.amount, new_ing.amount_offset );
+ }
+
+ break;
+ }
+ case 2: //unit
+ {
+ Unit old_unit = new_ing.units;
+
+ if ( new_text.length() > uint(database->maxUnitNameLength()) )
+ {
+ KMessageBox::error( this, QString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) );
+ ing_item->setUnit( old_unit );
+ break;
+ }
+
+ QString approp_unit = new_ing.amount > 1 ? new_ing.units.plural : new_ing.units.name;
+ if ( approp_unit != new_text.stripWhiteSpace() )
+ {
+ Unit new_unit;
+ int new_id = IngredientInputWidget::createNewUnitIfNecessary( new_text.stripWhiteSpace(), new_ing.amount > 1, ing_item->ingredient().ingredientID, new_unit, database );
+
+ if ( new_id != -1 ) {
+ new_ing.units = new_unit;
+ new_ing.units.id = new_id;
+
+ ing_item->setUnit( new_ing.units );
+
+ updatePropertyStatus();
+ emit changed();
+ }
+ else {
+ ing_item->setUnit( old_unit );
+ }
+ }
+ break;
+ }
+ case 3: //prep method
+ {
+ QString old_text = new_ing.prepMethodList.join(",");
+
+ QStringList prepMethodList = QStringList::split(",",new_text.stripWhiteSpace());
+
+ for ( QStringList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it ) {
+ if ( (*it).stripWhiteSpace().length() > uint(database->maxPrepMethodNameLength()) )
+ {
+ KMessageBox::error( this, QString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) );
+ ing_item->setPrepMethod( old_text );
+ break;
+ }
+ }
+
+ if ( old_text != new_text.stripWhiteSpace() )
+ {
+ new_ing.prepMethodList = ElementList::split(",",new_text.stripWhiteSpace());
+ QValueList<int> new_ids = IngredientInputWidget::createNewPrepIfNecessary( new_ing.prepMethodList, database );
+
+ QValueList<int>::const_iterator id_it = new_ids.begin();
+ for ( ElementList::iterator it = new_ing.prepMethodList.begin(); it != new_ing.prepMethodList.end(); ++it, ++id_it ) {
+ (*it).id = *id_it;
+ }
+
+ updatePropertyStatus();
+ emit changed();
+ }
+ break;
+ }
+ }
+}
+
+void RecipeInputDialog::recipeChanged( void )
+{
+ if ( changedSignalEnabled ) {
+ // Enable Save Button
+ emit enableSaveOption( true );
+ emit createButton( this, titleEdit->text() );
+ unsavedChanges = true;
+
+ }
+
+}
+
+void RecipeInputDialog::recipeChanged( const QString & /*t*/ )
+{
+ recipeChanged(); // jumps to the real slot function
+}
+
+void RecipeInputDialog::enableChangedSignal( bool en )
+{
+ changedSignalEnabled = en;
+}
+
+bool RecipeInputDialog::save ( void )
+{
+ //check bounds first
+ if ( titleEdit->text().length() > uint(database->maxRecipeTitleLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Recipe title cannot be longer than %1 characters." ) ).arg( database->maxRecipeTitleLength() ), i18n( "Unable to save recipe" ) );
+ return false;
+ }
+
+ emit enableSaveOption( false );
+ saveRecipe();
+ unsavedChanges = false;
+
+ return true;
+}
+
+void RecipeInputDialog::saveRecipe( void )
+{
+ // Nothing except for the ingredient list (loadedRecipe->ingList)
+ // was stored before for performance. (recipeID is already there)
+
+ loadedRecipe->photo = sourcePhoto;
+ loadedRecipe->instructions = instructionsEdit->text();
+ loadedRecipe->title = titleEdit->text();
+ yieldNumInput->value(loadedRecipe->yield.amount,loadedRecipe->yield.amount_offset);
+ loadedRecipe->yield.type_id = createNewYieldIfNecessary(yieldTypeEdit->text());
+ loadedRecipe->prepTime = prepTimeEdit->time();
+
+ // Now save()
+ kdDebug() << "Saving..." << endl;
+ database->saveRecipe( loadedRecipe );
+
+
+}
+
+void RecipeInputDialog::newRecipe( void )
+{
+ loadedRecipe->empty();
+ QPixmap image( defaultPhoto );
+ photoLabel->setPixmap( image );
+ sourcePhoto = QPixmap();
+
+ instructionsEdit->setText( i18n( "Write the recipe instructions here" ) );
+ instructionsEdit->clearCompletionItems();
+ titleEdit->setText( i18n( "Write the recipe title here" ) );
+ ingredientList->clear();
+ authorShow->clear();
+ categoryShow->clear();
+ yieldNumInput->setValue( 1, 0 );
+ yieldTypeEdit->setText("");
+ prepTimeEdit->setTime( QTime( 0, 0 ) );
+
+ instructionsEdit->selectAll();
+
+ //Set back to the first page
+ tabWidget->setCurrentPage( 0 );
+
+ ingInput->clear();
+
+ //Set focus to the title
+ titleEdit->setFocus();
+ titleEdit->selectAll();
+
+ //clear status info
+ propertyStatusMapRed.clear();
+ propertyStatusMapYellow.clear();
+ showStatusIndicator();
+}
+
+bool RecipeInputDialog::everythingSaved()
+{
+ return ( !( unsavedChanges ) );
+}
+
+void RecipeInputDialog::addCategory( void )
+{
+ SelectCategoriesDialog *editCategoriesDialog = new SelectCategoriesDialog( this, loadedRecipe->categoryList, database );
+
+ if ( editCategoriesDialog->exec() == QDialog::Accepted ) { // user presses Ok
+ loadedRecipe->categoryList.clear();
+ editCategoriesDialog->getSelectedCategories( &( loadedRecipe->categoryList ) ); // get the category list chosen
+ emit( recipeChanged() ); //Indicate that the recipe changed
+
+ }
+
+ delete editCategoriesDialog;
+
+ // show category list
+ showCategories();
+
+
+}
+
+void RecipeInputDialog::showCategories( void )
+{
+ QString categories;
+ for ( ElementList::const_iterator cat_it = loadedRecipe->categoryList.begin(); cat_it != loadedRecipe->categoryList.end(); ++cat_it ) {
+ if ( !categories.isEmpty() )
+ categories += ",";
+ categories += ( *cat_it ).name;
+ }
+ categoryShow->setText( categories );
+}
+
+void RecipeInputDialog::addAuthor( void )
+{
+ SelectAuthorsDialog * editAuthorsDialog = new SelectAuthorsDialog( this, loadedRecipe->authorList, database );
+
+
+ if ( editAuthorsDialog->exec() == QDialog::Accepted ) { // user presses Ok
+ loadedRecipe->authorList.clear();
+ editAuthorsDialog->getSelectedAuthors( &( loadedRecipe->authorList ) ); // get the category list chosen
+ emit( recipeChanged() ); //Indicate that the recipe changed
+ }
+
+ delete editAuthorsDialog;
+
+ // show authors list
+ showAuthors();
+}
+
+void RecipeInputDialog::showAuthors( void )
+{
+ QString authors;
+ for ( ElementList::const_iterator author_it = loadedRecipe->authorList.begin(); author_it != loadedRecipe->authorList.end(); ++author_it ) {
+ if ( !authors.isEmpty() )
+ authors += ",";
+ authors += ( *author_it ).name;
+ }
+ authorShow->setText( authors );
+}
+
+void RecipeInputDialog::enableSaveButton( bool enabled )
+{
+ saveButton->setEnabled( enabled );
+}
+
+void RecipeInputDialog::closeOptions( void )
+{
+
+ // First check if there's anything unsaved in the recipe
+ if ( unsavedChanges ) {
+
+ switch ( KMessageBox::questionYesNoCancel( this, i18n( "This recipe contains unsaved changes.\n" "Would you like to save it before closing?" ), i18n( "Unsaved changes" ) ) ) {
+ case KMessageBox::Yes:
+ save();
+ break;
+ case KMessageBox::No:
+ break;
+ case KMessageBox::Cancel:
+ return ;
+ }
+
+ }
+
+ emit enableSaveOption( false );
+ unsavedChanges = false;
+
+ // Now close really
+ emit closeRecipe();
+
+
+}
+
+void RecipeInputDialog::showRecipe( void )
+{
+ // First check if there's anything unsaved in the recipe
+
+ if ( loadedRecipe->recipeID == -1 ) {
+ switch ( KMessageBox::questionYesNo( this, i18n( "You need to save the recipe before displaying it. Would you like to save it now?" ), i18n( "Unsaved changes" ) ) ) {
+ case KMessageBox::Yes:
+ save();
+ break;
+ case KMessageBox::No:
+ return ;
+ }
+ }
+ else if ( unsavedChanges ) {
+
+ switch ( KMessageBox::questionYesNoCancel( this, i18n( "This recipe has changes that will not be displayed unless the recipe is saved. Would you like to save it now?" ), i18n( "Unsaved changes" ) ) ) {
+ case KMessageBox::Yes:
+ save();
+ break;
+ case KMessageBox::No:
+ break;
+ case KMessageBox::Cancel:
+ return ;
+ }
+
+ }
+
+ // Now open it really
+ emit showRecipe( loadedRecipe->recipeID );
+}
+
+void RecipeInputDialog::spellCheck( void )
+{
+ QString text = instructionsEdit->text();
+ KSpellConfig default_cfg( this );
+ KSpell::modalCheck( text, &default_cfg );
+ KMessageBox::information( this, i18n( "Spell check complete." ) );
+
+ if ( text != instructionsEdit->text() ) //check if there were changes
+ instructionsEdit->setText( text );
+}
+
+void RecipeInputDialog::resizeRecipe( void )
+{
+ yieldNumInput->value( loadedRecipe->yield.amount, loadedRecipe->yield.amount_offset );
+ ResizeRecipeDialog dlg( this, loadedRecipe );
+
+ if ( dlg.exec() == QDialog::Accepted )
+ reload();
+}
+
+int RecipeInputDialog::ingItemIndex( QListView *listview, const QListViewItem *item ) const
+{
+ if ( !item )
+ return -1;
+
+ if ( item == listview->firstChild() )
+ return 0;
+ else {
+ QListViewItemIterator it( listview->firstChild() );
+ uint j = 0;
+ for ( ; it.current() && it.current() != item; ++it ) {
+ if ( it.current() ->rtti() == INGLISTVIEWITEM_RTTI ) {
+ if ( !it.current()->parent() || it.current()->parent()->rtti() == INGGRPLISTVIEWITEM_RTTI )
+ j++;
+ }
+ }
+
+ if ( !it.current() )
+ return -1;
+
+ return j;
+ }
+}
+
+void RecipeInputDialog::slotIngredientParser()
+{
+ UnitList units;
+ database->loadUnits(&units);
+ IngredientParserDialog dlg(units,this);
+ if ( dlg.exec() == QDialog::Accepted ) {
+ IngredientList ings = dlg.ingredients();
+ QStringList usedGroups;
+ bool haveHeader = false;
+ for ( IngredientList::iterator it = ings.begin(); it != ings.end(); ++it ) {
+ if ( !(*it).group.isEmpty() && usedGroups.find((*it).group) == usedGroups.end() ) {
+ int id = IngredientInputWidget::createNewGroupIfNecessary((*it).group,database);
+ addIngredientHeader( Element((*it).group, id) );
+ haveHeader = true;
+ usedGroups << (*it).group;
+ }
+ (*it).ingredientID = IngredientInputWidget::createNewIngredientIfNecessary((*it).name,database);
+ (*it).units.id = IngredientInputWidget::createNewUnitIfNecessary((*it).units.name,false,(*it).ingredientID,(*it).units,database);
+
+ QValueList<int> prepIDs = IngredientInputWidget::createNewPrepIfNecessary((*it).prepMethodList,database);
+ QValueList<int>::const_iterator prep_id_it = prepIDs.begin();
+ for ( ElementList::iterator prep_it = (*it).prepMethodList.begin(); prep_it != (*it).prepMethodList.end(); ++prep_it, ++prep_id_it ) {
+ (*prep_it).id = *prep_id_it;
+ }
+
+ addIngredient( *it, !haveHeader );
+
+ if ( usedGroups.count() > 0 && (*it).group.isEmpty() ) {
+ QListViewItem *last_item = ingredientList->lastItem();
+ if ( last_item->parent() ) {
+ last_item->parent()->takeItem( last_item );
+ ingredientList->insertItem( last_item );
+ last_item->moveItem( ingredientList->lastItem()->parent() );
+ }
+ }
+ }
+ }
+}
+
+void RecipeInputDialog::slotAddRating()
+{
+ ElementList criteriaList;
+ database->loadRatingCriterion(&criteriaList);
+
+ EditRatingDialog ratingDlg(criteriaList,this);
+ if ( ratingDlg.exec() == QDialog::Accepted ) {
+ Rating r = ratingDlg.rating();
+
+ for ( RatingCriteriaList::iterator rc_it = r.ratingCriteriaList.begin(); rc_it != r.ratingCriteriaList.end(); ++rc_it ) {
+ int criteria_id = database->findExistingRatingByName((*rc_it).name);
+ if ( criteria_id == -1 ) {
+ database->createNewRating((*rc_it).name);
+ criteria_id = database->lastInsertID();
+ }
+ (*rc_it).id = criteria_id;
+ }
+
+ RatingDisplayWidget *item = new RatingDisplayWidget;
+ item->rating_it = loadedRecipe->ratingList.append(r);
+ addRating(r,item);
+ ratingListDisplayWidget->insertItem(item,0);
+ emit( recipeChanged() ); //Indicate that the recipe changed
+ }
+}
+
+void RecipeInputDialog::addRating( const Rating &rating, RatingDisplayWidget *item )
+{
+ int average = qRound(rating.average());
+ if ( average >= 0 )
+ item->icon->setPixmap( UserIcon(QString("rating%1").arg(average) ) );
+ else //no rating criteria, therefore no average (we don't want to automatically assume a zero average)
+ item->icon->clear();
+
+ item->raterName->setText(rating.rater);
+ item->comment->setText(rating.comment);
+
+ item->criteriaListView->clear();
+ for ( RatingCriteriaList::const_iterator rc_it = rating.ratingCriteriaList.begin(); rc_it != rating.ratingCriteriaList.end(); ++rc_it ) {
+ QListViewItem * it = new QListViewItem(item->criteriaListView,(*rc_it).name);
+
+ int stars = int((*rc_it).stars * 2); //multiply by two to make it easier to work with half-stars
+
+ QPixmap star = UserIcon(QString::fromLatin1("star_on"));
+ int pixmapWidth = 18*(stars/2)+((stars%2==1)?9:0);
+ QPixmap generatedPixmap(pixmapWidth,18);
+
+ if ( !generatedPixmap.isNull() ) { //there aren't zero stars
+ generatedPixmap.fill();
+ QPainter painter( &generatedPixmap );
+ painter.drawTiledPixmap(0,0,pixmapWidth,18,star);
+ it->setPixmap(1,generatedPixmap);
+ }
+ }
+
+ item->buttonEdit->disconnect();
+ item->buttonRemove->disconnect();
+ connect(item->buttonEdit, SIGNAL(clicked()),
+ this, SLOT(slotEditRating()));
+ connect(item->buttonRemove, SIGNAL(clicked()),
+ this, SLOT(slotRemoveRating()));
+}
+
+void RecipeInputDialog::slotEditRating()
+{
+ RatingDisplayWidget *sender = (RatingDisplayWidget*)(QObject::sender()->parent());
+
+ ElementList criteriaList;
+ database->loadRatingCriterion(&criteriaList);
+
+ EditRatingDialog ratingDlg(criteriaList,*sender->rating_it,this);
+ if ( ratingDlg.exec() == QDialog::Accepted ) {
+ Rating r = ratingDlg.rating();
+
+ for ( RatingCriteriaList::iterator rc_it = r.ratingCriteriaList.begin(); rc_it != r.ratingCriteriaList.end(); ++rc_it ) {
+ int criteria_id = database->findExistingRatingByName((*rc_it).name);
+ if ( criteria_id == -1 ) {
+ database->createNewRating((*rc_it).name);
+ criteria_id = database->lastInsertID();
+ }
+ (*rc_it).id = criteria_id;
+ }
+
+ (*sender->rating_it) = r;
+ addRating(r,sender);
+ emit recipeChanged(); //Indicate that the recipe changed
+ }
+}
+
+void RecipeInputDialog::slotRemoveRating()
+{
+ RatingDisplayWidget *sender = (RatingDisplayWidget*)(QObject::sender()->parent());
+ loadedRecipe->ratingList.remove(sender->rating_it);
+
+ //FIXME: sender is removed but never deleted (sender->deleteLater() doesn't work)
+ ratingListDisplayWidget->removeItem(sender);
+
+ emit recipeChanged(); //Indicate that the recipe changed
+}
+
+void RecipeInputDialog::addIngredient( const Ingredient &ing, bool noHeader )
+{
+ Ingredient ingCopy = ing;
+
+ //Append to the ListView
+ QListViewItem* lastElement = ingredientList->lastItem();
+ while ( lastElement && lastElement->rtti() == INGSUBLISTVIEWITEM_RTTI )
+ lastElement = lastElement->itemAbove();
+
+ if ( noHeader && lastElement )
+ lastElement = (lastElement->parent())?lastElement->parent():lastElement;
+
+ if ( !noHeader && lastElement &&
+ ( lastElement->rtti() == INGGRPLISTVIEWITEM_RTTI || ( lastElement->parent() && lastElement->parent() ->rtti() == INGGRPLISTVIEWITEM_RTTI ) ) )
+ {
+ IngGrpListViewItem * header = ( lastElement->parent() ) ? ( IngGrpListViewItem* ) lastElement->parent() : ( IngGrpListViewItem* ) lastElement;
+
+ ingCopy.groupID = header->id();
+
+ lastElement = new IngListViewItem( header, lastElement, ingCopy );
+ for ( QValueList<IngredientData>::const_iterator it = ingCopy.substitutes.begin(); it != ingCopy.substitutes.end(); ++it ) {
+ new IngSubListViewItem( lastElement, *it );
+ }
+ lastElement->setOpen(true);
+ }
+ else {
+ lastElement = new IngListViewItem( ingredientList, lastElement, ingCopy );
+ for ( QValueList<IngredientData>::const_iterator it = ing.substitutes.begin(); it != ing.substitutes.end(); ++it ) {
+ new IngSubListViewItem( lastElement, *it );
+ }
+ lastElement->setOpen(true);
+ }
+
+ //append to recipe
+ loadedRecipe->ingList.append( ingCopy );
+
+ //update the completion in the instructions edit
+ instructionsEdit->addCompletionItem( ingCopy.name );
+
+ updatePropertyStatus( ingCopy, true );
+
+ emit changed();
+}
+
+void RecipeInputDialog::addIngredientHeader( const Element &header )
+{
+ QListViewItem *last_item = ingredientList->lastItem();
+ if ( last_item && last_item->parent() )
+ last_item = last_item->parent();
+
+ IngGrpListViewItem *ing_header = new IngGrpListViewItem( ingredientList, last_item, header.name, header.id );
+ ing_header->setOpen( true );
+}
+
+void RecipeInputDialog::updatePropertyStatus()
+{
+ propertyStatusMapRed.clear();
+ propertyStatusMapYellow.clear();
+
+ for ( IngredientList::const_iterator ing_it = loadedRecipe->ingList.begin(); ing_it != loadedRecipe->ingList.end(); ++ing_it ) {
+ updatePropertyStatus( *ing_it, false );
+ }
+
+ showStatusIndicator();
+}
+
+void RecipeInputDialog::updatePropertyStatus( const Ingredient &ing, bool updateIndicator )
+{
+ IngredientPropertyList ingPropertyList;
+ database->loadProperties( &ingPropertyList, ing.ingredientID );
+
+ if ( ingPropertyList.count() == 0 ) {
+ propertyStatusMapRed.insert(ing.ingredientID,QString(i18n("<b>%1:</b> No nutrient information available")).arg(ing.name));
+ }
+
+ QMap<int,bool> ratioCache; //unit->conversion possible
+ IngredientPropertyList::const_iterator prop_it;
+ for ( prop_it = ingPropertyList.begin(); prop_it != ingPropertyList.end(); ++prop_it ) {
+ Ingredient result;
+
+ QMap<int,bool>::const_iterator cache_it = ratioCache.find((*prop_it).perUnit.id);
+ if ( cache_it == ratioCache.end() ) {
+ RecipeDB::ConversionStatus status = database->convertIngredientUnits( ing, (*prop_it).perUnit, result );
+ ratioCache.insert((*prop_it).perUnit.id,status==RecipeDB::Success||status==RecipeDB::MismatchedPrepMethod);
+
+ switch ( status ) {
+ case RecipeDB::Success: break;
+ case RecipeDB::MissingUnitConversion: {
+ if ( ing.units.type != Unit::Other && ing.units.type == (*prop_it).perUnit.type ) {
+ propertyStatusMapRed.insert(ing.ingredientID,QString(i18n("<b>%3:</b> Unit conversion missing for conversion from '%1' to '%2'"))
+ .arg(ing.units.name.isEmpty()?i18n("-No unit-"):ing.units.name)
+ .arg((*prop_it).perUnit.name)
+ .arg(ing.name));
+ } else {
+ WeightList weights = database->ingredientWeightUnits( ing.ingredientID );
+ QValueList< QPair<int,int> > usedIds;
+ QStringList missingConversions;
+ for ( WeightList::const_iterator weight_it = weights.begin(); weight_it != weights.end(); ++weight_it ) {
+ //skip entries that only differ in how it's prepared
+ QPair<int,int> usedPair((*weight_it).perAmountUnitID,(*weight_it).weightUnitID);
+ if ( usedIds.find(usedPair) != usedIds.end() )
+ continue;
+
+ QString toUnit = database->unitName((*weight_it).perAmountUnitID).name;
+ if ( toUnit.isEmpty() ) toUnit = i18n("-No unit-");
+
+ QString fromUnit = database->unitName((*weight_it).weightUnitID).name;
+ if ( fromUnit.isEmpty() ) fromUnit = i18n("-No unit-");
+
+ QString ingUnit = ing.units.name;
+ if ( ingUnit.isEmpty() ) ingUnit = i18n("-No unit-");
+
+ QString propUnit = (*prop_it).perUnit.name;
+ if ( propUnit.isEmpty() ) propUnit = i18n("-No unit-");
+
+ missingConversions << conversionPath( ingUnit, toUnit, fromUnit, propUnit);
+
+ usedIds << usedPair;
+ }
+ propertyStatusMapRed.insert(ing.ingredientID,QString(i18n("<b>%1:</b> Either an appropriate ingredient weight entry is needed, or Krecipes needs conversion information to perform one of the following conversions: %2"))
+ .arg(ing.name)
+ .arg("<ul><li>"+missingConversions.join("</li><li>")+"</li></ul>")
+ );
+ }
+ break;
+ }
+ case RecipeDB::MissingIngredientWeight:
+ propertyStatusMapRed.insert(ing.ingredientID,QString(i18n("<b>%1:</b> No ingredient weight entries")).arg(ing.name));
+ break;
+ case RecipeDB::MismatchedPrepMethod:
+ if ( ing.prepMethodList.count() == 0 )
+ propertyStatusMapRed.insert(ing.ingredientID,QString(i18n("<b>%1:</b> There is no ingredient weight entry for when no preparation method is specified")).arg(ing.name));
+ else
+ propertyStatusMapRed.insert(ing.ingredientID,QString(i18n("<b>%1:</b> There is no ingredient weight entry for when prepared in any of the following manners: %2")).arg(ing.name).arg("<ul><li>"+ing.prepMethodList.join("</li><li>")+"</li></ul>"));
+ break;
+ case RecipeDB::MismatchedPrepMethodUsingApprox:
+ propertyStatusMapYellow.insert(ing.ingredientID,QString(i18n("<b>%1:</b> There is no ingredient weight entry for when prepared in any of the following manners (defaulting to a weight entry without a preparation method specified): %2")).arg(ing.name).arg("<ul><li>"+ing.prepMethodList.join("</li><li>")+"</li></ul>"));
+ break;
+ default: kdDebug()<<"Code error: Unhandled conversion status code "<<status<<endl; break;
+ }
+ }
+ }
+
+ if ( updateIndicator )
+ showStatusIndicator();
+}
+
+void RecipeInputDialog::showStatusIndicator()
+{
+ if ( propertyStatusMapRed.count() == 0 ) {
+ if ( propertyStatusMapYellow.count() == 0 ) {
+ propertyStatusLed->setColor( Qt::green );
+ propertyStatusLabel->setText( i18n("Complete") );
+ propertyStatusButton->setEnabled(false);
+ }
+ else {
+ propertyStatusLed->setColor( Qt::yellow );
+ propertyStatusLabel->setText( i18n("Complete, but approximations made") );
+ propertyStatusButton->setEnabled(true);
+ }
+ }
+ else {
+ propertyStatusLed->setColor( Qt::red );
+ propertyStatusLabel->setText( i18n("Incomplete") );
+ propertyStatusButton->setEnabled(true);
+ }
+
+ if ( propertyStatusMapRed.count() == 0 && propertyStatusMapYellow.count() == 0 )
+ propertyStatusDialog->hide();
+ else
+ statusTextView->setText(statusMessage());
+}
+
+QString RecipeInputDialog::statusMessage() const
+{
+ QString statusMessage;
+
+ if ( propertyStatusMapRed.count() > 0 ) {
+ statusMessage.append( i18n("The nutrient information for this recipe is incomplete because the following information is missing:") );
+ statusMessage.append("<ul>");
+ for ( QMap<int,QString>::const_iterator it = propertyStatusMapRed.begin(); it != propertyStatusMapRed.end(); ++it ) {
+ statusMessage.append("<li>");
+ statusMessage.append(it.data());
+ statusMessage.append("</li>");
+ }
+ statusMessage.append("</ul>");
+ }
+
+ if ( propertyStatusMapYellow.count() > 0 ) {
+ statusMessage.append( i18n("The following approximations will be made when determining nutrient information:") );
+ statusMessage.append("<ul>");
+ for ( QMap<int,QString>::const_iterator it = propertyStatusMapYellow.begin(); it != propertyStatusMapYellow.end(); ++it ) {
+ statusMessage.append("<li>");
+ statusMessage.append(it.data());
+ statusMessage.append("</li>");
+ }
+ statusMessage.append("</ul>");
+ }
+
+ return statusMessage;
+}
+
+QString RecipeInputDialog::conversionPath( const QString &ingUnit, const QString &toUnit, const QString &fromUnit, const QString &propUnit ) const
+{
+ QString path = "'"+ingUnit+"'";
+
+ QString lastUnit = ingUnit;
+ if ( lastUnit != toUnit ) {
+ path.append(" =&gt; '"+toUnit+"'");
+ lastUnit = toUnit;
+ }
+ if ( lastUnit != fromUnit ) {
+ path.append(" =&gt; '"+fromUnit+"'");
+ lastUnit = fromUnit;
+ }
+ if ( lastUnit != propUnit ) {
+ path.append(" =&gt; '"+propUnit+"'");
+ lastUnit = propUnit;
+ }
+ return path;
+}
+
+#include "recipeinputdialog.moc"
diff --git a/krecipes/src/dialogs/recipeinputdialog.h b/krecipes/src/dialogs/recipeinputdialog.h
new file mode 100644
index 0000000..7b09345
--- /dev/null
+++ b/krecipes/src/dialogs/recipeinputdialog.h
@@ -0,0 +1,228 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEINPUTDIALOG_H
+#define RECIPEINPUTDIALOG_H
+
+#include <kdialog.h>
+#include <ktextedit.h>
+#include <klineedit.h>
+#include <kcombobox.h>
+#include <klistview.h>
+#include <knuminput.h>
+#include <kpushbutton.h>
+#include <kiconloader.h>
+#include <kled.h>
+
+#include <qlabel.h>
+#include <qgroupbox.h>
+#include <qmap.h>
+#include <qobject.h>
+#include <qtabwidget.h>
+#include <qtoolbutton.h>
+#include <qvbox.h>
+
+#include "datablocks/elementlist.h"
+
+class QTabWidget;
+class QTimeEdit;
+class QDragEvent;
+class QButtonGroup;
+class QWidgetStack;
+class QTextEdit;
+
+class KreTextEdit;
+class KWidgetListbox;
+class KDialogBase;
+
+class ImageDropLabel;
+class Recipe;
+class ElementList;
+class RecipeDB;
+class FractionInput;
+class Ingredient;
+class Rating;
+class RatingDisplayWidget;
+class IngredientInputWidget;
+class ClickableLed;
+
+/**
+@author Unai Garro
+*/
+class RecipeInputDialog: public QVBox
+{
+ Q_OBJECT
+
+public:
+
+ RecipeInputDialog( QWidget* parent, RecipeDB* db );
+ void loadRecipe( int recipeID );
+ ~RecipeInputDialog();
+ void newRecipe( void );
+ bool everythingSaved();
+ void reload( void );
+ int loadedRecipeID() const;
+
+private:
+
+ // Internal Data
+ Recipe *loadedRecipe; //Loaded Recipe
+ RecipeDB *database;
+ bool changedSignalEnabled;
+ bool unsavedChanges;
+
+ // Widgets
+ QTabWidget* tabWidget;
+ QGroupBox* recipeTab;
+ QGroupBox* instructionsTab;
+
+ //Recipe Photo
+ ImageDropLabel *photoLabel;
+ QPixmap sourcePhoto;
+ QPushButton *changePhotoButton;
+
+ //Recipe Body
+ KreTextEdit* instructionsEdit;
+ QLabel* titleLabel;
+ KLineEdit* titleEdit;
+
+ //Additional recipe data
+ QLabel* yieldLabel;
+ FractionInput* yieldNumInput;
+ KLineEdit* yieldTypeEdit;
+ QTimeEdit *prepTimeEdit;
+ QLabel* authorLabel;
+ KLineEdit* authorShow;
+ QPushButton* addAuthorButton;
+ QLabel* categoryLabel;
+ KLineEdit* categoryShow;
+ QPushButton* addCategoryButton;
+
+ //Ingredient inputs
+ KListView* ingredientList;
+ QGroupBox *ingredientGBox;
+ IngredientInputWidget *ingInput;
+ ClickableLed *propertyStatusLed;
+ QLabel *propertyStatusLabel;
+ QPushButton *propertyStatusButton;
+ QTextEdit *statusTextView;
+ KDialogBase *propertyStatusDialog;
+
+ // Buttons to move ingredients up & down...
+ KPushButton* upButton;
+ KPushButton* downButton;
+ KPushButton* removeButton;
+ KPushButton* addButton;
+ KPushButton* ingParserButton;
+
+ //Function buttons
+ QGroupBox* functionsBox;
+ QToolButton* saveButton;
+ QToolButton* closeButton;
+ QToolButton* showButton;
+ QToolButton* resizeButton;
+
+ QToolButton* spellCheckButton;
+
+ KWidgetListbox *ratingListDisplayWidget;
+
+ QMap<int,QString> propertyStatusMapRed;
+ QMap<int,QString> propertyStatusMapYellow;
+
+ // Internal functions
+ int createNewYieldIfNecessary( const QString &yield );
+ void saveRecipe( void );
+ void showCategories( void );
+ void showAuthors( void );
+ int ingItemIndex( QListView *listview, const QListViewItem *item ) const;
+ void addRating( const Rating &rating, RatingDisplayWidget *item );
+ QString statusMessage() const;
+ QString conversionPath( const QString &ingUnit, const QString &toUnit, const QString &fromUnit, const QString &propUnit ) const;
+
+ // Signals & Slots
+
+private slots:
+ void changePhoto( void );
+ void clearPhoto( void );
+ void moveIngredientUp( void );
+ void moveIngredientDown( void );
+ void removeIngredient( void );
+ void syncListView( QListViewItem* it, const QString &new_text, int col );
+ void recipeChanged( void );
+ void recipeChanged( const QString &t );
+ void enableChangedSignal( bool en = true );
+ void addCategory( void );
+ void addAuthor( void );
+ void enableSaveButton( bool enabled );
+ void closeOptions( void );
+ void showRecipe( void );
+ void prepTitleChanged( const QString &title );
+ void recipeRemoved( int id );
+ void slotIngredientParser();
+ void slotAddRating();
+ void slotEditRating();
+ void slotRemoveRating();
+ void addIngredient( const Ingredient &ing, bool noHeader = false );
+ void addIngredientHeader( const Element &header );
+ void updatePropertyStatus();
+ void updatePropertyStatus( const Ingredient &ing, bool updateIndicator );
+ void showStatusIndicator();
+
+public slots:
+ bool save ( void ); // Activated when krecipes.cpp sends signal save()
+ void spellCheck( void );
+ void resizeRecipe( void );
+
+signals:
+ void changed( void );
+ void closeRecipe( void );
+ void createButton( QWidget* w, const QString &title );
+ void enableSaveOption( bool en = true );
+ void showRecipe( int recipeID ); //Indicates krecipesview to show it
+ void titleChanged( const QString &title );
+
+
+};
+
+class ClickableLed : public KLed
+{
+Q_OBJECT
+
+public:
+ ClickableLed( QWidget *parent );
+
+protected:
+ virtual void mouseReleaseEvent( QMouseEvent* );
+
+signals:
+ void clicked();
+};
+
+class ImageDropLabel : public QLabel
+{
+ Q_OBJECT
+
+public:
+ ImageDropLabel( QWidget *parent, QPixmap &_sourcePhoto );
+
+signals:
+ void changed();
+
+protected:
+ void dragEnterEvent( QDragEnterEvent* event );
+ void dropEvent( QDropEvent* event );
+
+private:
+ QPixmap &sourcePhoto;
+};
+
+#endif
diff --git a/krecipes/src/dialogs/recipeprintpreview.cpp b/krecipes/src/dialogs/recipeprintpreview.cpp
new file mode 100644
index 0000000..ed90855
--- /dev/null
+++ b/krecipes/src/dialogs/recipeprintpreview.cpp
@@ -0,0 +1,62 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipeprintpreview.h"
+
+#include <qvbox.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "recipeviewdialog.h"
+#include "pagesetupdialog.h"
+
+RecipePrintPreview::RecipePrintPreview( QWidget *parent, RecipeDB *db, const QValueList<int> &ids )
+ : KDialogBase( parent, "RecipePrintPreview", true, i18n("Print Preview"),
+ KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::User1 | KDialogBase::Help, KDialogBase::Ok )
+{
+ setHelp("print-recipe");
+ setButtonText( KDialogBase::User1, i18n("&Edit") );
+ setButtonText( KDialogBase::Ok, i18n("&Print") );
+
+ setSizeGripEnabled( true );
+
+ // Initialize UI Elements
+ QVBox *page = makeVBoxMainWidget();
+
+ recipeView = new RecipeViewDialog( page, db );
+ recipeView->loadRecipes( ids, "Print" );
+
+ setInitialSize( QSize(450,500), false );
+}
+
+RecipePrintPreview::~RecipePrintPreview()
+{
+}
+
+void RecipePrintPreview::slotOk()
+{
+ recipeView->printNoPreview();
+ accept();
+}
+
+void RecipePrintPreview::slotUser1( void )
+{
+ PageSetupDialog pageSetup( this, Recipe(), "Print" );
+ if ( pageSetup.exec() == QDialog::Accepted )
+ reload();
+}
+
+void RecipePrintPreview::reload()
+{
+ recipeView->reload( "Print" );
+}
+
+#include "recipeprintpreview.moc"
diff --git a/krecipes/src/dialogs/recipeprintpreview.h b/krecipes/src/dialogs/recipeprintpreview.h
new file mode 100644
index 0000000..0e85613
--- /dev/null
+++ b/krecipes/src/dialogs/recipeprintpreview.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEPRINTPREVIEW_H
+#define RECIPEPRINTPREVIEW_H
+
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#include <kdialogbase.h>
+
+class RecipeDB;
+class RecipeViewDialog;
+
+class RecipePrintPreview : public KDialogBase
+{
+Q_OBJECT
+
+public:
+ RecipePrintPreview( QWidget *parent, RecipeDB *db, const QValueList<int> &ids );
+ ~RecipePrintPreview();
+
+ void reload();
+
+public slots:
+ void slotOk();
+ void slotUser1( void );
+
+private:
+ // Internal Variables
+ RecipeViewDialog *recipeView;
+};
+
+#endif
diff --git a/krecipes/src/dialogs/recipeviewdialog.cpp b/krecipes/src/dialogs/recipeviewdialog.cpp
new file mode 100644
index 0000000..9290f2c
--- /dev/null
+++ b/krecipes/src/dialogs/recipeviewdialog.cpp
@@ -0,0 +1,167 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipeviewdialog.h"
+
+#include <qlayout.h>
+#include <qstyle.h>
+#include <qfile.h>
+
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kdebug.h>
+#include <khtmlview.h>
+#include <khtml_part.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <kprogress.h>
+#include <kstandarddirs.h>
+#include <kstatusbar.h>
+#include <kconfig.h>
+#include <kglobal.h>
+
+#include "datablocks/mixednumber.h"
+#include "backends/recipedb.h"
+#include "exporters/htmlexporter.h"
+#include "recipeprintpreview.h"
+
+RecipeViewDialog::RecipeViewDialog( QWidget *parent, RecipeDB *db, int recipeID ) : QVBox( parent ),
+ database(db)
+{
+ // Initialize UI Elements
+ recipeView = new KHTMLPart( this );
+
+ connect( database, SIGNAL(recipeRemoved(int)), SLOT(recipeRemoved(int)) );
+
+ tmp_filename = locateLocal( "tmp", "krecipes_recipe_view" );
+
+ //----------Load the recipe --------
+ if ( recipeID != -1 )
+ loadRecipe( recipeID );
+}
+
+RecipeViewDialog::~RecipeViewDialog()
+{
+ if ( recipe_loaded )
+ removeOldFiles();
+}
+
+bool RecipeViewDialog::loadRecipe( int recipeID )
+{
+ QValueList<int> ids;
+ ids.append( recipeID );
+ return loadRecipes( ids );
+}
+
+bool RecipeViewDialog::loadRecipes( const QValueList<int> &ids, const QString &layoutConfig )
+{
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ // Remove any files created by the last recipe loaded
+ removeOldFiles();
+
+ ids_loaded = ids; //need to save these ids in order to delete the html files later...make sure this comes after the call to removeOldFiles()
+ recipe_loaded = ( ids.count() > 0 && ids[ 0 ] >= 0 );
+
+ bool success = showRecipes( ids, layoutConfig );
+
+ KApplication::restoreOverrideCursor();
+ return success;
+}
+
+bool RecipeViewDialog::showRecipes( const QValueList<int> &ids, const QString &layoutConfig )
+{
+ KProgressDialog * progress_dialog = 0;
+
+ if ( ids.count() > 1 ) //we don't want a progress bar coming up when there is only one recipe... it may show up during the splash screen
+ {
+ progress_dialog = new KProgressDialog( this, "open_progress_dialog", QString::null, i18n( "Opening recipes, please wait..." ), true );
+ progress_dialog->resize( 240, 80 );
+ }
+
+ HTMLExporter html_generator( tmp_filename + ".html", "html" );
+ if ( layoutConfig != QString::null ) {
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Page Setup" );
+ QString styleFile = config->readEntry( layoutConfig+"Layout", locate( "appdata", "layouts/Default.klo" ) );
+ if ( !styleFile.isEmpty() && QFile::exists( styleFile ) )
+ html_generator.setStyle( styleFile );
+
+ QString templateFile = config->readEntry( layoutConfig+"Template", locate( "appdata", "layouts/Default.template" ) );
+ if ( !templateFile.isEmpty() && QFile::exists( templateFile ) )
+ html_generator.setTemplate( templateFile );
+ }
+
+ html_generator.exporter( ids, database, progress_dialog ); //writes the generated HTML to 'tmp_filename+".html"'
+ if ( progress_dialog && progress_dialog->wasCancelled() ) {
+ delete progress_dialog;
+ return false;
+ }
+
+ delete recipeView; // Temporary workaround
+ recipeView = new KHTMLPart( this ); // to avoid the problem of caching images of KHTMLPart
+
+ KURL url;
+ url.setPath( tmp_filename + ".html" );
+ recipeView->openURL( url );
+ recipeView->show();
+ kdDebug() << "Opening URL: " << url.htmlURL() << endl;
+
+ delete progress_dialog;
+ return true;
+}
+
+void RecipeViewDialog::print()
+{
+ if ( recipe_loaded ) {
+ RecipePrintPreview preview( this, database, ids_loaded );
+ preview.exec();
+ }
+}
+
+void RecipeViewDialog::printNoPreview( void )
+{
+ if ( recipe_loaded ) {
+ recipeView->view() ->print();
+ }
+}
+
+void RecipeViewDialog::reload( const QString &layoutConfig )
+{
+ loadRecipes( ids_loaded, layoutConfig );
+}
+
+void RecipeViewDialog::removeOldFiles()
+{
+ if ( ids_loaded.count() > 0 ) {
+ RecipeList recipe_list;
+ database->loadRecipes( &recipe_list, RecipeDB::Title, ids_loaded );
+
+ QValueList<int> recipe_ids;
+ for ( RecipeList::const_iterator it = recipe_list.begin(); it != recipe_list.end(); ++it )
+ recipe_ids << ( *it ).recipeID;
+
+ HTMLExporter::removeHTMLFiles( tmp_filename, recipe_ids );
+ }
+}
+
+void RecipeViewDialog::recipeRemoved( int id )
+{
+ //if the deleted recipe is loaded, clean the view up
+ if ( ids_loaded.find(id) != ids_loaded.end() ) {
+ Recipe recipe; database->loadRecipe( &recipe, RecipeDB::Title, id );
+ HTMLExporter::removeHTMLFiles( tmp_filename, recipe.recipeID );
+ ids_loaded.remove(id);
+ }
+}
+
+#include "recipeviewdialog.moc"
diff --git a/krecipes/src/dialogs/recipeviewdialog.h b/krecipes/src/dialogs/recipeviewdialog.h
new file mode 100644
index 0000000..ffc7c12
--- /dev/null
+++ b/krecipes/src/dialogs/recipeviewdialog.h
@@ -0,0 +1,77 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEVIEWDIALOG_H
+#define RECIPEVIEWDIALOG_H
+
+#include <qvbox.h>
+#include <qstring.h>
+
+class RecipeDB;
+class Recipe;
+class KHTMLPart;
+class QPushButton;
+
+/**
+@author Unai Garro
+*/
+
+class RecipeViewDialog : public QVBox
+{
+ Q_OBJECT
+
+public:
+ RecipeViewDialog( QWidget *parent, RecipeDB *db, int recipeID = -1 );
+
+ ~RecipeViewDialog();
+
+ /** @return Boolean indicating whether or not the recipe was successfully loaded */
+ bool loadRecipe( int recipeID );
+
+ /** @return Boolean indicating whether or not the recipes were successfully loaded */
+ bool loadRecipes( const QValueList<int> &ids, const QString &layoutConfig = QString::null );
+
+ int recipesLoaded() const
+ {
+ return ids_loaded.count();
+ }
+ const QValueList<int> currentRecipes() const
+ {
+ return ids_loaded;
+ }
+
+ void reload( const QString &layoutConfig = QString::null );
+
+public slots:
+ void printNoPreview( void );
+ void print( void );
+
+private:
+
+ // Internal Variables
+ KHTMLPart *recipeView;
+ RecipeDB *database;
+ bool recipe_loaded;
+ QValueList<int> ids_loaded;
+ QString tmp_filename;
+
+ // Internal Methods
+ bool showRecipes( const QValueList<int> &ids, const QString &layoutConfig );
+ void removeOldFiles();
+
+private slots:
+ void recipeRemoved(int);
+
+};
+
+
+#endif
diff --git a/krecipes/src/dialogs/refineshoppinglistdialog.cpp b/krecipes/src/dialogs/refineshoppinglistdialog.cpp
new file mode 100644
index 0000000..b293150
--- /dev/null
+++ b/krecipes/src/dialogs/refineshoppinglistdialog.cpp
@@ -0,0 +1,206 @@
+/***************************************************************************
+* Copyright (C) 2004 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "refineshoppinglistdialog.h"
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <qheader.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kconfig.h>
+
+#include "backends/recipedb.h"
+#include "widgets/krelistview.h"
+#include "widgets/ingredientlistview.h"
+#include "shoppinglistviewdialog.h"
+#include "shoppingcalculator.h"
+#include "datablocks/mixednumber.h"
+
+RefineShoppingListDialog::RefineShoppingListDialog( QWidget* parent, RecipeDB *db, const ElementList &recipeList )
+ : KDialogBase( parent, "refinedialog", true, QString::null,
+ KDialogBase::Ok, KDialogBase::Ok ),
+ database( db )
+{
+ setButtonText( KDialogBase::Ok, i18n( "&Done" ) );
+
+ QVBox *page = makeVBoxMainWidget();
+
+ helpLabel = new QLabel( page, "helpLabel" );
+ helpLabel->setTextFormat( QLabel::RichText );
+
+ QWidget *layout2Widget = new QWidget(page);
+
+ QHBoxLayout *layout2 = new QHBoxLayout( layout2Widget, 0, 6, "layout2" );
+
+ allIngListView = new KreListView( layout2Widget, QString::null, true, 0 );
+ StdIngredientListView *list_view = new StdIngredientListView(allIngListView,database);
+ list_view->reload();
+ allIngListView->setListView(list_view);
+ layout2->addWidget( allIngListView );
+
+ layout1 = new QVBoxLayout( 0, 0, 6, "layout1" );
+
+ KIconLoader il;
+
+ addButton = new QPushButton( layout2Widget, "addButton" );
+ addButton->setIconSet( il.loadIconSet( "forward", KIcon::Small ) );
+ addButton->setFixedSize( QSize( 32, 32 ) );
+ layout1->addWidget( addButton );
+
+ removeButton = new QPushButton( layout2Widget, "removeButton" );
+ removeButton->setIconSet( il.loadIconSet( "back", KIcon::Small ) );
+ removeButton->setFixedSize( QSize( 32, 32 ) );
+ layout1->addWidget( removeButton );
+ spacer1 = new QSpacerItem( 51, 191, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ layout1->addItem( spacer1 );
+ layout2->addLayout( layout1 );
+
+ ingListView = new KreListView( layout2Widget, QString::null, true );
+ ingListView->listView() ->addColumn( i18n( "Ingredients in Shopping List" ) );
+ ingListView->listView() ->addColumn( i18n( "Amount" ) );
+ ingListView->listView() ->addColumn( i18n( "Unit" ) );
+ ingListView->listView() ->setItemsRenameable( true );
+ ingListView->listView() ->setRenameable( 0, false );
+ ingListView->listView() ->setRenameable( 1, true );
+ ingListView->listView() ->setRenameable( 2, true );
+ layout2->addWidget( ingListView );
+
+ languageChange();
+
+ clearWState( WState_Polished );
+
+ connect( addButton, SIGNAL( clicked() ), this, SLOT( addIngredient() ) );
+ connect( removeButton, SIGNAL( clicked() ), this, SLOT( removeIngredient() ) );
+ connect( ingListView->listView(), SIGNAL( itemRenamed( QListViewItem*, const QString &, int ) ), SLOT( itemRenamed( QListViewItem*, const QString &, int ) ) );
+
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+ calculateShopping( recipeList, &ingredientList, database );
+ KApplication::restoreOverrideCursor();
+
+ loadData();
+}
+
+RefineShoppingListDialog::~RefineShoppingListDialog()
+{}
+
+void RefineShoppingListDialog::languageChange()
+{
+ helpLabel->setText( i18n( "On the right are the ingredients needed for the recipes you selected. You may now add additional ingredients, remove ingredients you do not need, or modify the amounts of existing ingredients." ) );
+ allIngListView->listView() ->header() ->setLabel( 0, i18n( "Ingredients" ) );
+ ingListView->listView() ->header() ->setLabel( 0, i18n( "Ingredients in Shopping List" ) );
+ ingListView->listView() ->header() ->setLabel( 1, i18n( "Amount" ) );
+ ingListView->listView() ->header() ->setLabel( 2, i18n( "Unit" ) );
+}
+
+void RefineShoppingListDialog::accept()
+{
+ hide();
+
+ ShoppingListViewDialog view( this, ingredientList );
+ view.exec();
+
+ QDialog::accept();
+}
+
+void RefineShoppingListDialog::loadData()
+{
+ for ( IngredientList::iterator it = ingredientList.begin(); it != ingredientList.end(); ++it ) {
+ //from here on, the shopping list will work with the upper value of the range (if exists)
+ (*it).amount = (*it).amount+(*it).amount_offset;
+ (*it).amount_offset = 0;
+
+ QString amount_str;
+ if ( ( *it ).amount > 0 ) {
+ KConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+
+ if ( config->readBoolEntry( "Fraction" ) )
+ amount_str = MixedNumber( ( *it ).amount ).toString();
+ else
+ amount_str = beautify( KGlobal::locale() ->formatNumber( ( *it ).amount, 5 ) );
+ }
+
+ QListViewItem *new_item = new QListViewItem( ingListView->listView(), ( *it ).name, amount_str, ( *it ).amount > 1 ? ( *it ).units.plural : ( *it ).units.name );
+ item_ing_map.insert( new_item, it );
+ }
+}
+
+void RefineShoppingListDialog::addIngredient()
+{
+ QListViewItem * item = allIngListView->listView() ->selectedItem();
+ if ( item ) {
+ QListViewItem * new_item = new QListViewItem( ingListView->listView(), item->text( 0 ) );
+ ingListView->listView() ->setSelected( new_item, true );
+ ingListView->listView() ->ensureItemVisible( new_item );
+ allIngListView->listView() ->setSelected( item, false );
+
+ item_ing_map.insert( new_item, ingredientList.append( Ingredient( item->text( 0 ), 0, Unit() ) ) );
+ }
+}
+
+void RefineShoppingListDialog::removeIngredient()
+{
+ QListViewItem * item = ingListView->listView() ->selectedItem();
+ if ( item ) {
+ for ( IngredientList::iterator it = ingredientList.begin(); it != ingredientList.end(); ++it ) {
+ if ( *item_ing_map.find( item ) == it ) {
+ ingredientList.remove( it );
+ item_ing_map.remove( item );
+ break;
+ }
+ }
+ delete item;
+ }
+}
+
+void RefineShoppingListDialog::itemRenamed( QListViewItem* item, const QString &new_text, int col )
+{
+ if ( col == 1 ) {
+ IngredientList::iterator found_it = *item_ing_map.find( item );
+
+ bool ok;
+ MixedNumber amount = MixedNumber::fromString( new_text, &ok );
+ if ( ok ) {
+ ( *found_it ).amount = amount.toDouble();
+ }
+ else { //revert back to the valid amount string
+ QString amount_str;
+ if ( ( *found_it ).amount > 0 ) {
+ KConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+
+ if ( config->readBoolEntry( "Fraction" ) )
+ amount_str = MixedNumber( ( *found_it ).amount ).toString();
+ else
+ amount_str = beautify( KGlobal::locale() ->formatNumber( ( *found_it ).amount, 5 ) );
+ }
+
+ item->setText( 1, amount_str );
+ }
+ }
+ else if ( col == 2 ) {
+ IngredientList::iterator found_it = *item_ing_map.find( item );
+
+ if ( ( *found_it ).amount > 1 )
+ ( *found_it ).units.plural = new_text;
+ else
+ ( *found_it ).units.name = new_text;
+ }
+}
+
+#include "refineshoppinglistdialog.moc"
diff --git a/krecipes/src/dialogs/refineshoppinglistdialog.h b/krecipes/src/dialogs/refineshoppinglistdialog.h
new file mode 100644
index 0000000..08c4bb9
--- /dev/null
+++ b/krecipes/src/dialogs/refineshoppinglistdialog.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+* Copyright (C) 2004 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef REFINESHOPPINGLISTDIALOG_H
+#define REFINESHOPPINGLISTDIALOG_H
+
+#include <qvariant.h>
+#include <qmap.h>
+
+#include <kdialogbase.h>
+
+#include "datablocks/ingredientlist.h"
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QLabel;
+class QListViewItem;
+class QPushButton;
+
+class RecipeDB;
+class ElementList;
+class KreListView;
+
+class RefineShoppingListDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ RefineShoppingListDialog( QWidget* parent, RecipeDB *db, const ElementList &recipeList );
+ ~RefineShoppingListDialog();
+
+ QLabel* helpLabel;
+ KreListView* allIngListView;
+ QPushButton* addButton;
+ QPushButton* removeButton;
+ KreListView* ingListView;
+
+protected:
+ QVBoxLayout* layout1;
+ QSpacerItem* spacer1;
+
+protected slots:
+ virtual void languageChange();
+ virtual void accept();
+ void addIngredient();
+ void removeIngredient();
+ void itemRenamed( QListViewItem*, const QString &, int );
+
+private:
+ void loadData();
+
+ RecipeDB *database;
+ IngredientList ingredientList;
+ QMap<QListViewItem*, IngredientList::iterator> item_ing_map;
+};
+
+#endif // REFINESHOPPINGLISTDIALOG_H
diff --git a/krecipes/src/dialogs/resizerecipedialog.cpp b/krecipes/src/dialogs/resizerecipedialog.cpp
new file mode 100644
index 0000000..6d4131c
--- /dev/null
+++ b/krecipes/src/dialogs/resizerecipedialog.cpp
@@ -0,0 +1,191 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "resizerecipedialog.h"
+
+#include <cmath>
+
+#include <qvbox.h>
+#include <qvariant.h>
+#include <qbuttongroup.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <knuminput.h>
+#include <klineedit.h>
+#include <qradiobutton.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qimage.h>
+#include <qpixmap.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include "datablocks/recipe.h"
+#include "widgets/fractioninput.h"
+
+#define FACTOR_RADIO_BUTTON 0
+#define SERVINGS_RADIO_BUTTON 1
+
+ResizeRecipeDialog::ResizeRecipeDialog( QWidget *parent, Recipe *recipe )
+ : KDialogBase( parent, "ResizeRecipeDialog", true, i18n( "Resize Recipe" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ),
+ m_recipe( recipe )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ buttonGroup = new QButtonGroup( page );
+ buttonGroup->setSizePolicy( QSizePolicy( ( QSizePolicy::SizeType ) 5, ( QSizePolicy::SizeType ) 7, 0, 1, buttonGroup->sizePolicy().hasHeightForWidth() ) );
+ buttonGroup->setLineWidth( 0 );
+ buttonGroup->setColumnLayout( 0, Qt::Vertical );
+ buttonGroup->layout() ->setSpacing( 6 );
+ buttonGroup->layout() ->setMargin( 11 );
+ buttonGroupLayout = new QVBoxLayout( buttonGroup->layout() );
+ buttonGroupLayout->setAlignment( Qt::AlignTop );
+
+ yieldRadioButton = new QRadioButton( buttonGroup );
+ buttonGroup->insert( yieldRadioButton, SERVINGS_RADIO_BUTTON );
+ buttonGroupLayout->addWidget( yieldRadioButton );
+
+ yieldFrame = new QFrame( buttonGroup );
+ yieldFrame->setFrameShape( QFrame::Box );
+ yieldFrame->setFrameShadow( QFrame::Sunken );
+ yieldFrame->setLineWidth( 1 );
+ yieldFrameLayout = new QGridLayout( yieldFrame, 1, 1, 11, 6 );
+
+ currentYieldLabel = new QLabel( yieldFrame );
+
+ yieldFrameLayout->addWidget( currentYieldLabel, 0, 0 );
+
+ newYieldLabel = new QLabel( yieldFrame );
+
+ yieldFrameLayout->addMultiCellWidget( newYieldLabel, 1, 1, 0, 1 );
+
+ currentYieldInput = new KLineEdit( yieldFrame );
+ currentYieldInput->setReadOnly( TRUE );
+ currentYieldInput->setAlignment( Qt::AlignRight );
+ yieldFrameLayout->addMultiCellWidget( currentYieldInput, 0, 0, 1, 2 );
+
+ newYieldInput = new FractionInput( yieldFrame );
+ yieldFrameLayout->addWidget( newYieldInput, 1, 2 );
+
+ buttonGroupLayout->addWidget( yieldFrame );
+
+ factorRadioButton = new QRadioButton( buttonGroup );
+ buttonGroup->insert( factorRadioButton, FACTOR_RADIO_BUTTON );
+ buttonGroupLayout->addWidget( factorRadioButton );
+
+ factorFrame = new QFrame( buttonGroup );
+ factorFrame->setSizePolicy( QSizePolicy( ( QSizePolicy::SizeType ) 7, ( QSizePolicy::SizeType ) 5, 1, 0, factorFrame->sizePolicy().hasHeightForWidth() ) );
+ factorFrame->setFrameShape( QFrame::Box );
+ factorFrame->setFrameShadow( QFrame::Sunken );
+ factorFrame->setLineWidth( 1 );
+ factorFrameLayout = new QHBoxLayout( factorFrame, 11, 6 );
+
+ factorLabel = new QLabel( factorFrame );
+ factorFrameLayout->addWidget( factorLabel );
+
+ factorInput = new FractionInput( factorFrame );
+ factorInput->setSizePolicy( QSizePolicy( ( QSizePolicy::SizeType ) 7, ( QSizePolicy::SizeType ) 5, 0, 0, factorInput->sizePolicy().hasHeightForWidth() ) );
+ factorFrameLayout->addWidget( factorInput );
+ buttonGroupLayout->addWidget( factorFrame );
+
+ languageChange();
+
+
+ newYieldInput->setValue( m_recipe->yield.amount, 0 ); //Ignore the range info, it doesn't work in this context
+ currentYieldInput->setText( m_recipe->yield.toString() );
+
+ if ( recipe->yield.amount_offset > 0 ) {
+ yieldRadioButton->setEnabled(false);
+ buttonGroup->setButton( FACTOR_RADIO_BUTTON );
+ activateCurrentOption( FACTOR_RADIO_BUTTON );
+ }
+ else {
+ buttonGroup->setButton( SERVINGS_RADIO_BUTTON );
+ activateCurrentOption( SERVINGS_RADIO_BUTTON );
+ }
+
+ // signals and slots connections
+ connect( buttonGroup, SIGNAL( clicked( int ) ), this, SLOT( activateCurrentOption( int ) ) );
+}
+
+void ResizeRecipeDialog::languageChange()
+{
+ buttonGroup->setTitle( QString::null );
+ yieldRadioButton->setText( i18n( "Scale by yield" ) );
+ newYieldLabel->setText( i18n( "New yield:" ) );
+ currentYieldLabel->setText( i18n( "Current yield:" ) );
+ factorRadioButton->setText( i18n( "Scale by factor" ) );
+ factorLabel->setText( i18n( "Factor (i.e. 1/2 to half, 3 to triple):" ) );
+}
+
+void ResizeRecipeDialog::activateCurrentOption( int button_id )
+{
+ switch ( button_id ) {
+ case SERVINGS_RADIO_BUTTON:
+ factorFrame->setEnabled( false );
+ yieldFrame->setEnabled( true );
+ break;
+ case FACTOR_RADIO_BUTTON:
+ factorFrame->setEnabled( true );
+ yieldFrame->setEnabled( false );
+ break;
+ default:
+ break;
+ }
+}
+
+void ResizeRecipeDialog::accept()
+{
+ if ( currentYieldInput->text().toInt() == 0 )
+ KMessageBox::error( this, i18n( "Unable to scale a recipe with zero yield" ) );
+ else if ( buttonGroup->selected() == yieldRadioButton ) {
+ if ( newYieldInput->isInputValid() ) {
+ double new_yield = newYieldInput->value().toDouble();
+ double current_yield = MixedNumber::fromString(currentYieldInput->text()).toDouble();
+
+ resizeRecipe( new_yield / current_yield );
+ }
+ else {
+ KMessageBox::error( this, i18n( "Invalid input" ) );
+ newYieldInput->selectAll();
+ return;
+ }
+ }
+ else {
+ if ( factorInput->isInputValid() && factorInput->value() > 0 )
+ resizeRecipe( factorInput->value().toDouble() );
+ else {
+ KMessageBox::error( this, i18n( "Invalid input" ) );
+ factorInput->selectAll();
+ return ;
+ }
+ }
+
+ QDialog::accept();
+}
+
+//TODO YIELD: handle ranges
+void ResizeRecipeDialog::resizeRecipe( double factor )
+{
+ m_recipe->yield.amount = MixedNumber::fromString(currentYieldInput->text()).toDouble() * factor;
+
+ for ( IngredientList::iterator ing_it = m_recipe->ingList.begin(); ing_it != m_recipe->ingList.end(); ++ing_it ) {
+ ( *ing_it ).amount = ( *ing_it ).amount * factor;
+ ( *ing_it ).amount_offset = ( *ing_it ).amount_offset * factor;
+ }
+}
+
+#include "resizerecipedialog.moc"
diff --git a/krecipes/src/dialogs/resizerecipedialog.h b/krecipes/src/dialogs/resizerecipedialog.h
new file mode 100644
index 0000000..219e79d
--- /dev/null
+++ b/krecipes/src/dialogs/resizerecipedialog.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RESIZERECIPEDIALOG_H
+#define RESIZERECIPEDIALOG_H
+
+#include <kdialogbase.h>
+
+class Recipe;
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class FractionInput;
+class QButtonGroup;
+class QFrame;
+class QLabel;
+class KIntNumInput;
+class KLineEdit;
+class QRadioButton;
+class QPushButton;
+
+/**
+ *@author Jason Kivlighn
+ */
+class ResizeRecipeDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ ResizeRecipeDialog( QWidget *parent, Recipe* );
+
+protected slots:
+ void accept();
+ void activateCurrentOption( int );
+ virtual void languageChange();
+
+private:
+ void resizeRecipe( double factor );
+
+ Recipe *m_recipe;
+
+ QButtonGroup* buttonGroup;
+ QRadioButton* yieldRadioButton;
+ QFrame* yieldFrame;
+ QLabel* currentYieldLabel;
+ QLabel* newYieldLabel;
+ KLineEdit* currentYieldInput;
+ FractionInput* newYieldInput;
+ QRadioButton* factorRadioButton;
+ QFrame* factorFrame;
+ QLabel* factorLabel;
+ FractionInput* factorInput;
+
+ QVBoxLayout* buttonGroupLayout;
+ QGridLayout* yieldFrameLayout;
+ QHBoxLayout* factorFrameLayout;
+};
+
+#endif //RESIZERECIPEDIALOG_H
diff --git a/krecipes/src/dialogs/selectauthorsdialog.cpp b/krecipes/src/dialogs/selectauthorsdialog.cpp
new file mode 100644
index 0000000..9618253
--- /dev/null
+++ b/krecipes/src/dialogs/selectauthorsdialog.cpp
@@ -0,0 +1,181 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "selectauthorsdialog.h"
+
+#include <qmessagebox.h>
+#include <qvbox.h>
+
+#include <kconfig.h>
+#include <kdialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kglobal.h>
+
+#include "backends/recipedb.h"
+
+SelectAuthorsDialog::SelectAuthorsDialog( QWidget *parent, const ElementList &currentAuthors, RecipeDB *db )
+ : KDialogBase( parent, "SelectAuthorsDialog", true, i18n("Authors"),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ),
+ database(db)
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ //Design UI
+
+ // Combo to Pick authors
+ QHBox *topBox = new QHBox(page);
+ topBox->setSpacing(6);
+
+ authorsCombo = new KComboBox( true, topBox );
+ authorsCombo->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+ authorsCombo->completionObject() ->setCompletionMode( KGlobalSettings::CompletionPopupAuto );
+ authorsCombo->lineEdit() ->disconnect( authorsCombo ); //so hitting enter doesn't enter the item into the box
+
+ // Add/Remove buttons
+
+ il = new KIconLoader;
+ addAuthorButton = new QPushButton( topBox );
+ QPixmap pm = il->loadIcon( "down", KIcon::NoGroup, 16 );
+ addAuthorButton->setIconSet( pm );
+
+ removeAuthorButton = new QPushButton( topBox );
+ pm = il->loadIcon( "up", KIcon::NoGroup, 16 );
+ removeAuthorButton->setIconSet( pm );
+
+ // Author List
+
+ authorListView = new KListView( page );
+ authorListView->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ authorListView->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+ authorListView->addColumn( i18n( "Author" ) );
+ authorListView->setAllColumnsShowFocus( true );
+
+ // Load the list
+ loadAuthors( currentAuthors );
+
+ adjustSize();
+ resize(450, height());
+
+ // Connect signals & Slots
+ connect ( addAuthorButton, SIGNAL( clicked() ), this, SLOT( addAuthor() ) );
+ connect ( removeAuthorButton, SIGNAL( clicked() ), this, SLOT( removeAuthor() ) );
+
+ authorsCombo->setEditText(QString::null);
+ authorsCombo->lineEdit()->setFocus();
+}
+
+SelectAuthorsDialog::~SelectAuthorsDialog()
+{}
+
+void SelectAuthorsDialog::getSelectedAuthors( ElementList *newAuthors )
+{
+
+ for ( QListViewItem * it = authorListView->firstChild();it; it = it->nextSibling() ) {
+ Element author;
+ author.id = it->text( 0 ).toInt();
+ author.name = it->text( 1 );
+ newAuthors->append( author );
+ }
+
+}
+
+void SelectAuthorsDialog::loadAuthors( const ElementList &currentAuthors )
+{
+
+ // Load the combo
+ reloadAuthorsCombo();
+
+ // Load the ListView with the authors of this recipe
+ authorListView->clear();
+ for ( ElementList::const_iterator author_it = currentAuthors.begin(); author_it != currentAuthors.end(); ++author_it ) {
+ ( void ) new QListViewItem( authorListView, QString::number( ( *author_it ).id ), ( *author_it ).name );
+ }
+
+}
+
+void SelectAuthorsDialog::addAuthor( void )
+{
+ //check bounds first
+ if ( authorsCombo->currentText().length() > uint(database->maxAuthorNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Author name cannot be longer than %1 characters." ) ).arg( database->maxAuthorNameLength() ) );
+ authorsCombo->lineEdit() ->selectAll();
+ return ;
+ }
+
+ if ( authorsCombo->lineEdit()->text().isEmpty() )
+ return;
+
+ if ( authorsCombo->contains( authorsCombo->currentText() ) )
+ authorsCombo->setCurrentItem( authorsCombo->currentText() );
+
+ createNewAuthorIfNecessary();
+
+ int currentItem = authorsCombo->currentItem();
+ Element currentElement = authorList.getElement( currentItem );
+
+ ( void ) new QListViewItem( authorListView, QString::number( currentElement.id ), currentElement.name );
+
+}
+
+void SelectAuthorsDialog::removeAuthor( void )
+{
+ // Find the selected item first
+ QListViewItem * it;
+ it = authorListView->selectedItem();
+
+ if ( it ) { // Check if an author is selected first
+ delete it;
+ }
+
+}
+
+void SelectAuthorsDialog::createNewAuthorIfNecessary( void )
+{
+
+ if ( !authorsCombo->contains( authorsCombo->currentText() ) &&
+ !( authorsCombo->currentText().stripWhiteSpace() ).isEmpty() ) // author is not in the list and is not empty
+ { // Create new author
+ QString newAuthorName = authorsCombo->currentText();
+ database->createNewAuthor( newAuthorName );
+ //List again the authors
+ reloadAuthorsCombo();
+
+ // Select the newly created author
+ authorsCombo->setCurrentItem( newAuthorName );
+ }
+}
+
+
+void SelectAuthorsDialog::reloadAuthorsCombo( void )
+{
+
+ //Load the author list
+ database->loadAuthors( &authorList );
+
+ // Load combo with all the authors
+ authorsCombo->clear();
+ authorsCombo->completionObject() ->clear();
+
+ for ( ElementList::const_iterator author_it = authorList.begin(); author_it != authorList.end(); ++author_it ) {
+ authorsCombo->insertItem( ( *author_it ).name );
+ authorsCombo->completionObject() ->addItem( ( *author_it ).name );
+ }
+
+}
+
+
+#include "selectauthorsdialog.moc"
diff --git a/krecipes/src/dialogs/selectauthorsdialog.h b/krecipes/src/dialogs/selectauthorsdialog.h
new file mode 100644
index 0000000..0417ca2
--- /dev/null
+++ b/krecipes/src/dialogs/selectauthorsdialog.h
@@ -0,0 +1,66 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SELECTAUTHORSDIALOG_H
+#define SELECTAUTHORSDIALOG_H
+
+#include <qlayout.h>
+#include <qpushbutton.h>
+
+#include <kdialogbase.h>
+#include <kiconloader.h>
+#include <klistview.h>
+#include <kcombobox.h>
+
+#include "datablocks/elementlist.h"
+
+class RecipeDB;
+
+/**
+@author Unai Garro
+*/
+class SelectAuthorsDialog: public KDialogBase
+{
+
+ Q_OBJECT
+
+public:
+
+ SelectAuthorsDialog( QWidget *parent, const ElementList &currentAuthors, RecipeDB *db );
+ ~SelectAuthorsDialog();
+ void getSelectedAuthors( ElementList *newAuthors );
+
+private:
+
+ //Widgets
+ KComboBox *authorsCombo;
+ KListView *authorListView;
+ QPushButton *okButton;
+ QPushButton *cancelButton;
+ QPushButton *addAuthorButton;
+ QPushButton *removeAuthorButton;
+ KIconLoader *il;
+
+ //Variables
+ ElementList authorList;
+ RecipeDB *database;
+
+ //Private methods
+ void loadAuthors( const ElementList &authorList );
+ void createNewAuthorIfNecessary( void );
+ void reloadAuthorsCombo( void );
+private slots:
+ void addAuthor( void );
+ void removeAuthor( void );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/selectcategoriesdialog.cpp b/krecipes/src/dialogs/selectcategoriesdialog.cpp
new file mode 100644
index 0000000..bfe57db
--- /dev/null
+++ b/krecipes/src/dialogs/selectcategoriesdialog.cpp
@@ -0,0 +1,104 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "selectcategoriesdialog.h"
+#include "createcategorydialog.h"
+
+#include <qvbox.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kdialog.h>
+#include <kmessagebox.h>
+
+#include "datablocks/categorytree.h"
+#include "backends/recipedb.h"
+#include "widgets/categorylistview.h"
+
+SelectCategoriesDialog::SelectCategoriesDialog( QWidget *parent, const ElementList &items_on, RecipeDB *db )
+ : KDialogBase( parent, "SelectCategoriesDialog", true, i18n("Categories"),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ),
+ database(db)
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ //Design UI
+
+ //Category List
+ categoryListView = new CategoryCheckListView( page, db, true, items_on );
+ categoryListView->reload();
+
+ //New category button
+ QPushButton *newCatButton = new QPushButton( page );
+ newCatButton->setText( i18n( "&New Category..." ) );
+ newCatButton->setFlat( true );
+
+ // Load the list
+ loadCategories( items_on );
+
+ setSizeGripEnabled( true );
+
+ // Connect signals & Slots
+ connect ( newCatButton, SIGNAL( clicked() ), SLOT( createNewCategory() ) );
+}
+
+SelectCategoriesDialog::~SelectCategoriesDialog()
+{}
+
+void SelectCategoriesDialog::getSelectedCategories( ElementList *newSelected )
+{
+ *newSelected = categoryListView->selections();
+}
+
+void SelectCategoriesDialog::loadCategories( const ElementList &items_on )
+{
+ categoryListView->populateAll();
+
+ ElementList::const_iterator it;
+ for ( it = items_on.begin(); it != items_on.end(); ++it ) {
+ CategoryCheckListItem *new_item = (CategoryCheckListItem*)categoryListView->findItem(QString::number((*it).id),1);
+ if ( new_item ) {
+ new_item->setOn(true);
+ }
+ }
+}
+
+void SelectCategoriesDialog::createNewCategory( void )
+{
+ ElementList categories;
+ database->loadCategories( &categories );
+ CreateCategoryDialog* categoryDialog = new CreateCategoryDialog( this, categories );
+
+ if ( categoryDialog->exec() == QDialog::Accepted ) {
+ QString result = categoryDialog->newCategoryName();
+ int subcategory = categoryDialog->subcategory();
+
+ //check bounds first
+ if ( result.length() > uint(database->maxCategoryNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Category name cannot be longer than %1 characters." ) ).arg( database->maxCategoryNameLength() ) );
+ return ;
+ }
+
+ database->createNewCategory( result, subcategory ); // Create the new category in the database
+
+ //a listview item will automatically be created, but we need to turn it on
+ Element new_cat( result, database->lastInsertID() );
+ QCheckListItem *new_item = ((QCheckListItem*)categoryListView->findItem( QString::number(new_cat.id), 1 ));
+ if ( new_item )
+ new_item->setOn(true);
+ }
+
+ delete categoryDialog;
+}
+
+
+#include "selectcategoriesdialog.moc"
diff --git a/krecipes/src/dialogs/selectcategoriesdialog.h b/krecipes/src/dialogs/selectcategoriesdialog.h
new file mode 100644
index 0000000..e9851b6
--- /dev/null
+++ b/krecipes/src/dialogs/selectcategoriesdialog.h
@@ -0,0 +1,57 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SELECTCATEGORIESDIALOG_H
+#define SELECTCATEGORIESDIALOG_H
+
+#include <qlayout.h>
+#include <qpushbutton.h>
+
+#include <klistview.h>
+#include <kdialogbase.h>
+
+#include "datablocks/elementlist.h"
+
+class CategoryTree;
+class CategoryCheckListItem;
+class CategoryCheckListView;
+class RecipeDB;
+
+/**
+@author Unai Garro
+*/
+class SelectCategoriesDialog: public KDialogBase
+{
+
+ Q_OBJECT
+
+public:
+
+ SelectCategoriesDialog( QWidget *parent, const ElementList &items_on, RecipeDB* db );
+ ~SelectCategoriesDialog();
+ void getSelectedCategories( ElementList *selected );
+private:
+
+ //Widgets
+ CategoryCheckListView *categoryListView;
+
+ //Variables
+ RecipeDB *database;
+
+ //Private methods
+ void loadCategories( const ElementList &items_on );
+
+private slots:
+ void createNewCategory( void );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/selectpropertydialog.cpp b/krecipes/src/dialogs/selectpropertydialog.cpp
new file mode 100644
index 0000000..acf3c79
--- /dev/null
+++ b/krecipes/src/dialogs/selectpropertydialog.cpp
@@ -0,0 +1,123 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "selectpropertydialog.h"
+
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#include "datablocks/ingredientpropertylist.h"
+
+SelectPropertyDialog::SelectPropertyDialog( QWidget* parent, IngredientPropertyList *propertyList, UnitList *unitList, OptionFlag showEmpty )
+ : KDialogBase( parent, "SelectPropertyDialog", true, i18n( "Choose Property" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ), m_showEmpty(showEmpty)
+{
+
+ // Initialize internal variables
+ unitListBack = new UnitList;
+
+ // Initialize Widgets
+ QVBox *page = makeVBoxMainWidget();
+
+ box = new QGroupBox( page );
+ box->setTitle( i18n( "Choose Property" ) );
+ box->setColumnLayout( 0, Qt::Vertical );
+ box->layout() ->setSpacing( 6 );
+ box->layout() ->setMargin( 11 );
+ QVBoxLayout *boxLayout = new QVBoxLayout( box->layout() );
+ boxLayout->setAlignment( Qt::AlignTop );
+
+ propertyChooseView = new KListView( box, "propertyChooseView" );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ propertyChooseView->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ propertyChooseView->addColumn( i18n( "Property" ) );
+ propertyChooseView->setAllColumnsShowFocus( true );
+ boxLayout->addWidget( propertyChooseView );
+
+ QHBoxLayout *layout2 = new QHBoxLayout;
+
+ perUnitsLabel = new QLabel( box );
+ perUnitsLabel->setGeometry( QRect( 5, 285, 100, 30 ) );
+ perUnitsLabel->setText( i18n( "Per units:" ) );
+ layout2->addWidget( perUnitsLabel );
+
+ perUnitsBox = new KComboBox( FALSE, box );
+ layout2->addWidget( perUnitsBox );
+ boxLayout->addLayout( layout2 );
+
+ resize( QSize( 200, 380 ).expandedTo( minimumSizeHint() ) );
+ clearWState( WState_Polished );
+
+ // Load data
+ loadProperties( propertyList );
+ loadUnits( unitList );
+}
+
+
+SelectPropertyDialog::~SelectPropertyDialog()
+{}
+
+
+int SelectPropertyDialog::propertyID( void )
+{
+
+ QListViewItem * it;
+ if ( ( it = propertyChooseView->selectedItem() ) ) {
+ return ( it->text( 0 ).toInt() );
+ }
+ else
+ return ( -1 );
+}
+
+int SelectPropertyDialog::perUnitsID()
+{
+
+ int comboCount = perUnitsBox->count();
+ if ( comboCount > 0 ) { // If not, the list may be empty (no list defined) and crashes while reading as seen before. So check just in case.
+ int comboID = perUnitsBox->currentItem();
+ return ( *unitListBack->at( comboID ) ).id;
+ }
+ else
+ return ( -1 );
+}
+
+void SelectPropertyDialog::loadProperties( IngredientPropertyList *propertyList )
+{
+ for ( IngredientPropertyList::const_iterator prop_it = propertyList->begin(); prop_it != propertyList->end(); ++prop_it ) {
+ ( void ) new QListViewItem( propertyChooseView, QString::number( (*prop_it).id ), (*prop_it).name );
+ }
+}
+void SelectPropertyDialog::loadUnits( UnitList *unitList )
+{
+ for ( UnitList::const_iterator unit_it = unitList->begin(); unit_it != unitList->end(); ++unit_it ) {
+ QString unitName = ( *unit_it ).name;
+ if ( unitName.isEmpty() ) {
+ if ( m_showEmpty == ShowEmptyUnit )
+ unitName = " "+i18n("-No unit-");
+ else
+ continue;
+ }
+
+ // Insert in the combobox
+ perUnitsBox->insertItem( unitName );
+
+ // Store with index for using later
+ Unit newUnit( *unit_it );
+ newUnit.name = unitName;
+ unitListBack->append( newUnit );
+ }
+}
diff --git a/krecipes/src/dialogs/selectpropertydialog.h b/krecipes/src/dialogs/selectpropertydialog.h
new file mode 100644
index 0000000..f3eacf9
--- /dev/null
+++ b/krecipes/src/dialogs/selectpropertydialog.h
@@ -0,0 +1,60 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SELECTPROPERTYDIALOG_H
+#define SELECTPROPERTYDIALOG_H
+
+#include <qwidget.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qvbox.h>
+
+#include <klistview.h>
+#include <kcombobox.h>
+#include <kdialogbase.h>
+
+#include "datablocks/unit.h"
+
+class IngredientPropertyList;
+
+/**
+@author Unai Garro
+*/
+class SelectPropertyDialog: public KDialogBase
+{
+public:
+ typedef enum OptionFlag { ShowEmptyUnit, HideEmptyUnit } ;
+
+ // Methods
+ SelectPropertyDialog( QWidget* parent, IngredientPropertyList *propertyList, UnitList *unitList, OptionFlag showEmpty = ShowEmptyUnit );
+ ~SelectPropertyDialog();
+ int propertyID( void );
+ int perUnitsID( void );
+private:
+ //Widgets
+ QGroupBox *box;
+ KListView *propertyChooseView;
+ QLabel *perUnitsLabel;
+ KComboBox *perUnitsBox;
+ OptionFlag m_showEmpty;
+
+ void loadProperties( IngredientPropertyList *propertyList );
+ void loadUnits( UnitList *unitList );
+
+ //Internal variables
+ UnitList *unitListBack; // To store unit list with ID's for later use
+
+};
+
+#endif
diff --git a/krecipes/src/dialogs/selectrecipedialog.cpp b/krecipes/src/dialogs/selectrecipedialog.cpp
new file mode 100644
index 0000000..502e5dc
--- /dev/null
+++ b/krecipes/src/dialogs/selectrecipedialog.cpp
@@ -0,0 +1,257 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "selectrecipedialog.h"
+
+#include <qsignalmapper.h>
+#include <qtabwidget.h>
+#include <qtooltip.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kprogress.h>
+#include <kmessagebox.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kcursor.h>
+#include <kiconloader.h>
+
+#include "advancedsearchdialog.h"
+#include "datablocks/categorytree.h"
+#include "backends/recipedb.h"
+#include "datablocks/recipe.h"
+#include "selectunitdialog.h"
+#include "createelementdialog.h"
+#include "recipefilter.h"
+#include "widgets/recipelistview.h"
+#include "widgets/categorylistview.h"
+#include "widgets/categorycombobox.h"
+
+SelectRecipeDialog::SelectRecipeDialog( QWidget *parent, RecipeDB* db )
+ : QWidget( parent )
+{
+ //Store pointer to Recipe Database
+ database = db;
+
+ QVBoxLayout *tabLayout = new QVBoxLayout( this );
+ tabWidget = new QTabWidget( this );
+ tabWidget->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+ tabLayout->addWidget( tabWidget );
+
+ basicSearchTab = new QGroupBox( this );
+ basicSearchTab->setFrameStyle( QFrame::NoFrame );
+ basicSearchTab->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+ //Design dialog
+
+ layout = new QGridLayout( basicSearchTab, 1, 1, 0, 0 );
+
+ // Border Spacers
+ QSpacerItem* spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addMultiCell( spacer_left, 1, 4, 0, 0 );
+ QSpacerItem* spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addMultiCell( spacer_top, 0, 0, 1, 4 );
+
+ searchBar = new QHBox( basicSearchTab );
+ searchBar->setSpacing( 7 );
+ layout->addWidget( searchBar, 1, 1 );
+
+ KIconLoader *il = new KIconLoader;
+ QPushButton *clearSearchButton = new QPushButton( searchBar );
+ clearSearchButton->setPixmap( il->loadIcon( "locationbar_erase", KIcon::NoGroup, 16 ) );
+
+ searchLabel = new QLabel( searchBar );
+ searchLabel->setText( i18n( "Search:" ) );
+ searchLabel->setFixedWidth( searchLabel->fontMetrics().width( i18n( "Search:" ) ) + 5 );
+ searchBox = new KLineEdit( searchBar );
+
+ QSpacerItem* searchSpacer = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( searchSpacer, 1, 2 );
+
+ #ifdef ENABLE_SLOW
+ categoryBox = new CategoryComboBox( basicSearchTab, database );
+ layout->addWidget( categoryBox, 1, 3 );
+ #endif
+
+ QSpacerItem* spacerFromSearchBar = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerFromSearchBar, 2, 1 );
+
+ recipeListView = new RecipeListView( basicSearchTab, database );
+ recipeListView->reload();
+ recipeListView->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Expanding );
+ layout->addMultiCellWidget( recipeListView, 3, 3, 1, 3 );
+
+ buttonBar = new QHBox( basicSearchTab );
+ layout->addMultiCellWidget( buttonBar, 4, 4, 1, 3 );
+
+ openButton = new QPushButton( buttonBar );
+ openButton->setText( i18n( "Open Recipe(s)" ) );
+ openButton->setDisabled( true );
+ QPixmap pm = il->loadIcon( "ok", KIcon::NoGroup, 16 );
+ openButton->setIconSet( pm );
+ editButton = new QPushButton( buttonBar );
+ editButton->setText( i18n( "Edit Recipe" ) );
+ editButton->setDisabled( true );
+ pm = il->loadIcon( "edit", KIcon::NoGroup, 16 );
+ editButton->setIconSet( pm );
+ removeButton = new QPushButton( buttonBar );
+ removeButton->setText( i18n( "Delete" ) );
+ removeButton->setDisabled( true );
+ removeButton->setMaximumWidth( 100 );
+ pm = il->loadIcon( "editshred", KIcon::NoGroup, 16 );
+ removeButton->setIconSet( pm );
+
+ tabWidget->insertTab( basicSearchTab, i18n( "Basic" ) );
+
+ advancedSearch = new AdvancedSearchDialog( this, database );
+ tabWidget->insertTab( advancedSearch, i18n( "Advanced" ) );
+
+ QToolTip::add( clearSearchButton, i18n( "Clear search" ) );
+
+ //Takes care of all recipe actions and provides a popup menu to 'recipeListView'
+ actionHandler = new RecipeActionsHandler( recipeListView, database );
+
+ recipeFilter = new RecipeFilter( recipeListView );
+
+ // Signals & Slots
+
+ connect( openButton, SIGNAL( clicked() ), actionHandler, SLOT( open() ) );
+ connect( this, SIGNAL( recipeSelected( bool ) ), openButton, SLOT( setEnabled( bool ) ) );
+ connect( editButton, SIGNAL( clicked() ), actionHandler, SLOT( edit() ) );
+ connect( this, SIGNAL( recipeSelected( bool ) ), editButton, SLOT( setEnabled( bool ) ) );
+ connect( removeButton, SIGNAL( clicked() ), actionHandler, SLOT( remove() ) );
+ connect( this, SIGNAL( recipeSelected( bool ) ), removeButton, SLOT( setEnabled( bool ) ) );
+
+ connect( clearSearchButton, SIGNAL( clicked() ), this, SLOT( clearSearch() ) );
+
+ KConfig * config = kapp->config();
+ config->setGroup( "Performance" );
+ if ( config->readBoolEntry("SearchAsYouType",true) ) {
+ connect( searchBox, SIGNAL( returnPressed( const QString& ) ), recipeFilter, SLOT( filter( const QString& ) ) );
+ connect( searchBox, SIGNAL( textChanged( const QString& ) ), this, SLOT( ensurePopulated() ) );
+ connect( searchBox, SIGNAL( textChanged( const QString& ) ), recipeFilter, SLOT( filter( const QString& ) ) );
+ }
+ else {
+ connect( searchBox, SIGNAL( returnPressed( const QString& ) ), this, SLOT( ensurePopulated() ) );
+ connect( searchBox, SIGNAL( returnPressed( const QString& ) ), recipeFilter, SLOT( filter( const QString& ) ) );
+ }
+
+ connect( recipeListView, SIGNAL( selectionChanged() ), this, SLOT( haveSelectedItems() ) );
+ #ifdef ENABLE_SLOW
+ connect( recipeListView, SIGNAL( nextGroupLoaded() ), categoryBox, SLOT( loadNextGroup() ) );
+ connect( recipeListView, SIGNAL( prevGroupLoaded() ), categoryBox, SLOT( loadPrevGroup() ) );
+ connect( categoryBox, SIGNAL( activated( int ) ), this, SLOT( filterComboCategory( int ) ) );
+ #endif
+ connect( recipeListView, SIGNAL( nextGroupLoaded() ), SLOT( refilter() ) );
+ connect( recipeListView, SIGNAL( prevGroupLoaded() ), SLOT( refilter() ) );
+
+ connect( advancedSearch, SIGNAL( recipeSelected( int, int ) ), SIGNAL( recipeSelected( int, int ) ) );
+ connect( advancedSearch, SIGNAL( recipesSelected( const QValueList<int> &, int ) ), SIGNAL( recipesSelected( const QValueList<int> &, int ) ) );
+
+ connect( actionHandler, SIGNAL( recipeSelected( int, int ) ), SIGNAL( recipeSelected( int, int ) ) );
+ connect( actionHandler, SIGNAL( recipesSelected( const QValueList<int> &, int ) ), SIGNAL( recipesSelected( const QValueList<int> &, int ) ) );
+
+ delete il;
+}
+
+SelectRecipeDialog::~SelectRecipeDialog()
+{
+ delete recipeFilter;
+}
+
+void SelectRecipeDialog::clearSearch()
+{
+ searchBox->setText( QString::null );
+ recipeFilter->filter( QString::null );
+}
+
+void SelectRecipeDialog::reload( ReloadFlags flag )
+{
+ recipeListView->reload(flag);
+
+ #ifdef ENABLE_SLOW
+ categoryBox->reload();
+ filterComboCategory( categoryBox->currentItem() );
+ #endif
+}
+
+void SelectRecipeDialog::refilter()
+{
+ if ( !searchBox->text().isEmpty() ) {
+ ensurePopulated();
+ recipeFilter->filter(searchBox->text());
+ }
+}
+
+void SelectRecipeDialog::ensurePopulated()
+{
+ recipeListView->populateAll();
+}
+
+void SelectRecipeDialog::haveSelectedItems()
+{
+ if ( recipeListView->selectedItems().count() > 0 )
+ emit recipeSelected( true );
+ else
+ emit recipeSelected( false );
+}
+
+void SelectRecipeDialog::getCurrentRecipe( Recipe *recipe )
+{
+ QPtrList<QListViewItem> items = recipeListView->selectedItems();
+ if ( items.count() == 1 && items.at(0)->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* )items.at(0);
+ database->loadRecipe( recipe, RecipeDB::All, recipe_it->recipeID() );
+ }
+}
+
+void SelectRecipeDialog::filterComboCategory( int row )
+{
+ recipeListView->populateAll(); //TODO: this would be faster if we didn't need to load everything first
+
+ kdDebug() << "I got row " << row << "\n";
+
+ //First get the category ID corresponding to this combo row
+ int categoryID = categoryBox->id( row );
+
+ //Now filter
+ recipeFilter->filterCategory( categoryID ); // if categoryID==-1 doesn't filter
+ recipeFilter->filter( searchBox->text() );
+
+ if ( categoryID != -1 ) {
+ QListViewItemIterator it( recipeListView );
+ while ( it.current() ) {
+ QListViewItem *item = it.current();
+ if ( item->isVisible() ) {
+ item->setOpen( true ); //will only open if already populated
+ //(could be the selected category's parent
+ if ( !item->firstChild() ) {
+ recipeListView->open( item ); //populates and opens the selected category
+ break;
+ }
+ }
+ ++it;
+ }
+
+ }
+}
+
+RecipeActionsHandler* SelectRecipeDialog::getActionsHandler() const
+{
+ if ( tabWidget->currentPage() == basicSearchTab )
+ return actionHandler;
+ else
+ return advancedSearch->actionHandler;
+}
+
+#include "selectrecipedialog.moc"
diff --git a/krecipes/src/dialogs/selectrecipedialog.h b/krecipes/src/dialogs/selectrecipedialog.h
new file mode 100644
index 0000000..ae689e6
--- /dev/null
+++ b/krecipes/src/dialogs/selectrecipedialog.h
@@ -0,0 +1,99 @@
+/**************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn(jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SELECTRECIPEDIALOG_H
+#define SELECTRECIPEDIALOG_H
+
+
+#include <qwidget.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qcursor.h>
+#include <qintdict.h>
+
+#include <kcombobox.h>
+#include <klineedit.h>
+#include <klistview.h>
+#include <kdialog.h>
+#include <kfiledialog.h>
+#include <kpopupmenu.h>
+
+#include "recipeactionshandler.h"
+#include "widgets/dblistviewbase.h"
+
+class QHBox;
+class QGroupBox;
+
+class RecipeDB;
+class ElementList;
+class Recipe;
+class AdvancedSearchDialog;
+class CategoryTree;
+class RecipeFilter;
+class RecipeListView;
+class CategoryComboBox;
+
+/**
+@author Unai Garro
+*/
+class SelectRecipeDialog : public QWidget
+{
+ Q_OBJECT
+public:
+ SelectRecipeDialog( QWidget *parent, RecipeDB *db );
+ ~SelectRecipeDialog();
+
+
+ //Public Methods
+ void getCurrentRecipe( Recipe *recipe );
+
+ RecipeActionsHandler * getActionsHandler() const;
+
+private:
+
+ // Widgets
+ QGridLayout *layout;
+ QTabWidget *tabWidget;
+ QGroupBox *basicSearchTab;
+ QHBox *searchBar;
+ RecipeListView* recipeListView;
+ QHBox *buttonBar;
+ QPushButton *openButton;
+ QPushButton *removeButton;
+ QPushButton *editButton;
+ QLabel *searchLabel;
+ KLineEdit *searchBox;
+ CategoryComboBox *categoryBox;
+ AdvancedSearchDialog *advancedSearch;
+ // Internal Data
+ RecipeDB *database;
+ RecipeActionsHandler *actionHandler;
+ RecipeFilter *recipeFilter;
+
+signals:
+ void recipeSelected( int id, int action );
+ void recipesSelected( const QValueList<int> &ids, int action );
+ void recipeSelected( bool );
+
+private slots:
+ void filterComboCategory( int row );
+ void refilter();
+ void ensurePopulated();
+
+public slots:
+ void haveSelectedItems();
+ void reload( ReloadFlags flag = Load );
+ void clearSearch();
+};
+
+#endif
diff --git a/krecipes/src/dialogs/selectunitdialog.cpp b/krecipes/src/dialogs/selectunitdialog.cpp
new file mode 100644
index 0000000..fff6c29
--- /dev/null
+++ b/krecipes/src/dialogs/selectunitdialog.cpp
@@ -0,0 +1,77 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "selectunitdialog.h"
+
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+SelectUnitDialog::SelectUnitDialog( QWidget* parent, const UnitList &unitList, OptionFlag showEmpty )
+ : KDialogBase( parent, "SelectUnitDialog", true, i18n( "Choose Unit" ),
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ), m_showEmpty(showEmpty)
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ box = new QGroupBox( page );
+ box->setTitle( i18n( "Choose Unit" ) );
+ box->setColumnLayout( 0, Qt::Vertical );
+ QVBoxLayout *boxLayout = new QVBoxLayout( box->layout() );
+
+ unitChooseView = new KListView( box );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ unitChooseView->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ unitChooseView->addColumn( i18n( "Unit" ) );
+ unitChooseView->setSorting(1);
+ unitChooseView->setGeometry( QRect( 5, 30, 180, 250 ) );
+ unitChooseView->setAllColumnsShowFocus( true );
+ boxLayout->addWidget( unitChooseView );
+
+ resize( QSize( 200, 350 ) );
+
+ loadUnits( unitList );
+}
+
+
+SelectUnitDialog::~SelectUnitDialog()
+{}
+
+int SelectUnitDialog::unitID( void )
+{
+
+ QListViewItem * it;
+ if ( ( it = unitChooseView->selectedItem() ) ) {
+ return ( it->text( 0 ).toInt() );
+ }
+ else
+ return ( -1 );
+}
+
+void SelectUnitDialog::loadUnits( const UnitList &unitList )
+{
+ for ( UnitList::const_iterator unit_it = unitList.begin(); unit_it != unitList.end(); ++unit_it ) {
+ QString unitName = ( *unit_it ).name;
+ if ( unitName.isEmpty() ) {
+ if ( m_showEmpty == ShowEmptyUnit )
+ unitName = " "+i18n("-No unit-");
+ else
+ continue;
+ }
+
+ ( void ) new QListViewItem( unitChooseView, QString::number( ( *unit_it ).id ), unitName );
+ }
+}
+
diff --git a/krecipes/src/dialogs/selectunitdialog.h b/krecipes/src/dialogs/selectunitdialog.h
new file mode 100644
index 0000000..f9371ca
--- /dev/null
+++ b/krecipes/src/dialogs/selectunitdialog.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SELECTUNITDIALOG_H
+#define SELECTUNITDIALOG_H
+
+#include <qwidget.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qvbox.h>
+
+#include <klistview.h>
+#include <kdialogbase.h>
+
+#include "datablocks/unit.h"
+
+/**
+@author Unai Garro
+*/
+class SelectUnitDialog : public KDialogBase
+{
+public:
+ typedef enum OptionFlag { ShowEmptyUnit, HideEmptyUnit } ;
+
+ SelectUnitDialog( QWidget* parent, const UnitList &unitList, OptionFlag = ShowEmptyUnit );
+
+ ~SelectUnitDialog();
+
+ int unitID( void );
+
+private:
+ //Widgets
+ QGroupBox *box;
+ KListView *unitChooseView;
+ OptionFlag m_showEmpty;
+
+ void loadUnits( const UnitList &unitList );
+
+};
+
+#endif
diff --git a/krecipes/src/dialogs/setupdisplay.cpp b/krecipes/src/dialogs/setupdisplay.cpp
new file mode 100644
index 0000000..1ab66d8
--- /dev/null
+++ b/krecipes/src/dialogs/setupdisplay.cpp
@@ -0,0 +1,602 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "setupdisplay.h"
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kfontdialog.h>
+#include <kcolordialog.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+
+#include <khtmlview.h>
+#include <dom/dom_doc.h>
+#include <dom/css_rule.h>
+
+#include <qinputdialog.h>
+#include <qaction.h>
+#include <qlabel.h>
+#include <qfile.h>
+#include <qregexp.h>
+#include <qtextedit.h>
+#include <qtooltip.h>
+#include <qobjectlist.h>
+#include <qvaluelist.h>
+#include <qlayout.h>
+
+#include "datablocks/mixednumber.h"
+#include "dialogs/borderdialog.h"
+#include "exporters/htmlexporter.h"
+
+#include <cmath>
+
+KreDisplayItem::KreDisplayItem( const QString &n, const QString &_name ) : nodeId(n), name(_name)
+{
+ clear();
+}
+
+void KreDisplayItem::clear()
+{
+ alignment = Qt::AlignHCenter;
+ show = true;
+ backgroundColor = QColor(255,255,255);
+ textColor = QColor(0,0,0);
+ columns = 1;
+}
+
+SetupDisplay::SetupDisplay( const Recipe &sample, QWidget *parent ) : KHTMLPart(parent),
+ box_properties( new PropertiesMap ),
+ node_item_map( new QMap<QString, KreDisplayItem*> ),
+ has_changes( false ),
+ popup(0)
+{
+ connect( this, SIGNAL( popupMenu(const QString &,const QPoint &) ), SLOT( nodeClicked(const QString &,const QPoint &) ) );
+
+ if ( sample.recipeID != -1 )
+ m_sample = sample;
+ else {
+ m_sample.title = i18n("Recipe Title");
+ m_sample.yield.amount = 0;
+ m_sample.categoryList.append( Element(i18n( "Category 1, Category 2, ..." ) ) );
+ m_sample.instructions = i18n("Instructions");
+ m_sample.prepTime = QTime(0,0);
+
+ m_sample.authorList.append( Element(i18n( "Author 1, Author 2, ..." )) );
+
+ Ingredient ing;
+ ing.name = i18n("Ingredient 1");
+ m_sample.ingList.append( ing );
+
+ ing.name = i18n("Ingredient 2");
+ m_sample.ingList.append( ing );
+
+ ing.name = "...";
+ m_sample.ingList.append( ing );
+
+ RatingCriteria rc;
+ Rating rating1;
+ rating1.rater = i18n("Rater");
+ rating1.comment = i18n("Comment");
+
+ rc.name = i18n("Criteria 1");
+ rc.stars = 5.0;
+ rating1.append(rc);
+
+ rc.name = i18n("Criteria 2");
+ rc.stars = 2.5;
+ rating1.append(rc);
+
+ IngredientProperty prop;
+ prop.name = i18n("Property 1");
+ m_sample.properties.append(prop);
+ prop.name = i18n("Property 2");
+ m_sample.properties.append(prop);
+ prop.name = "...";
+ m_sample.properties.append(prop);
+
+ m_sample.ratingList.append(rating1);
+ }
+
+ kdDebug()<<"first load"<<endl;
+ loadHTMLView();
+ show();
+
+ createItem( "background", i18n("Background"), BackgroundColor );
+ createItem( "title", i18n("Title"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+ createItem( "instructions", i18n("Instructions"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+ createItem( "prep_time", i18n("Preparation Time"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+ createItem( "photo", i18n("Photo"), Visibility | Border );
+ createItem( "authors", i18n("Authors"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+ createItem( "categories", i18n("Categories"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+ createItem( "ingredients", i18n("Ingredients"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border | Columns );
+ createItem( "properties", i18n("Properties"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border | Columns );
+ createItem( "ratings", i18n("Ratings"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+ createItem( "yield", i18n("Yield"), Font | BackgroundColor | TextColor | Visibility | Alignment | Border );
+}
+
+SetupDisplay::~SetupDisplay()
+{
+ delete box_properties;
+ delete node_item_map;
+}
+
+void SetupDisplay::loadHTMLView( const QString &templateFile, const QString &styleFile )
+{
+ kdDebug()<<"loading template: "<<templateFile<<" style: "<<styleFile<<endl;
+ QString tmp_filename = locateLocal( "tmp", "krecipes_recipe_view" );
+ HTMLExporter exporter( tmp_filename + ".html", "html" );
+ if ( templateFile != QString::null )
+ exporter.setTemplate( templateFile );
+ if ( styleFile != QString::null )
+ exporter.setStyle( styleFile );
+
+ RecipeList recipeList;
+ recipeList.append(m_sample);
+
+ QFile file(tmp_filename + ".html");
+ if ( file.open( IO_WriteOnly ) ) {
+ QTextStream stream(&file);
+ exporter.writeStream(stream,recipeList);
+ }
+ else {
+ kdDebug()<<"Unable to open file for writing"<<endl;
+ }
+ file.close();
+
+ KURL url;
+ url.setPath( tmp_filename + ".html" );
+ openURL( url );
+ kdDebug() << "Opening URL: " << url.htmlURL() << endl;
+}
+
+void SetupDisplay::reload()
+{
+ loadHTMLView( m_activeTemplate, m_activeStyle );
+}
+
+void SetupDisplay::loadTemplate( const QString &filename )
+{
+ bool storeChangedState = has_changes;
+ KTempFile tmpFile;
+ saveLayout(tmpFile.name());
+ has_changes = storeChangedState; //saveLayout() sets changes to false
+
+ loadHTMLView( filename, tmpFile.name() );
+
+ m_activeTemplate = filename;
+}
+
+void SetupDisplay::createItem( const QString &node, const QString &name, unsigned int properties )
+{
+ KreDisplayItem * item = new KreDisplayItem( node, name );
+ box_properties->insert( item, properties );
+ node_item_map->insert( node, item );
+}
+
+void SetupDisplay::loadLayout( const QString &filename )
+{
+ QFile input( filename );
+ if ( input.open( IO_ReadOnly ) ) {
+ QDomDocument doc;
+ QString error;
+ int line;
+ int column;
+ if ( !doc.setContent( &input, &error, &line, &column ) ) {
+ kdDebug() << QString( i18n( "\"%1\" at line %2, column %3. This may not be a Krecipes layout file." ) ).arg( error ).arg( line ).arg( column ) << endl;
+ return ;
+ }
+
+ m_styleSheet = DOM::CSSStyleSheet();
+
+ QMap<QString,KreDisplayItem*>::iterator it;
+ for ( it = node_item_map->begin(); it != node_item_map->end(); ++it ) {
+ it.data()->clear();
+ }
+ processDocument( doc );
+
+ loadHTMLView(m_activeTemplate, filename);
+ m_activeStyle = filename;
+
+ has_changes = false;
+ }
+ else
+ kdDebug() << "Unable to open file: " << filename << endl;
+}
+
+void SetupDisplay::beginObject( const QString &object )
+{
+ QMap<QString, KreDisplayItem*>::iterator map_it = node_item_map->find( object );
+ if ( map_it != node_item_map->end() )
+ m_currentItem = map_it.data();
+ else
+ m_currentItem = 0;
+}
+
+void SetupDisplay::endObject()
+{
+ m_currentItem = 0;
+}
+
+void SetupDisplay::loadBackgroundColor( const QString &object, const QColor &color )
+{
+ if ( m_currentItem ) {
+ m_currentItem->backgroundColor = color;
+ m_styleSheet.insertRule("."+object+" { "+bgColorAsCSS(color)+" }",m_styleSheet.cssRules().length());
+ }
+}
+
+void SetupDisplay::loadFont( const QString &object, const QFont &font )
+{
+ if ( m_currentItem ) {
+ m_currentItem->font = font;
+ m_styleSheet.insertRule("."+object+" { "+fontAsCSS(font)+" }",m_styleSheet.cssRules().length());
+ }
+}
+
+void SetupDisplay::loadTextColor( const QString &object, const QColor &color )
+{
+ if ( m_currentItem ) {
+ m_currentItem->textColor = color;
+ m_styleSheet.insertRule("."+object+" { "+textColorAsCSS(color)+" }",m_styleSheet.cssRules().length());
+ }
+}
+
+void SetupDisplay::loadVisibility( const QString &object, bool visible )
+{
+ if ( m_currentItem ) {
+ m_currentItem->show = visible;
+ emit itemVisibilityChanged( m_currentItem, visible );
+
+ m_styleSheet.insertRule("."+object+" { "+visibilityAsCSS(visible)+" }",m_styleSheet.cssRules().length());
+ }
+}
+
+void SetupDisplay::loadAlignment( const QString &object, int alignment )
+{
+ if ( m_currentItem ) {
+ m_currentItem->alignment = alignment;
+ m_styleSheet.insertRule("."+object+" { "+alignmentAsCSS(alignment)+" }",m_styleSheet.cssRules().length());
+ }
+}
+
+void SetupDisplay::loadBorder( const QString &object, const KreBorder& border )
+{
+ if ( m_currentItem ) {
+ m_currentItem->border = border;
+ m_styleSheet.insertRule("."+object+" { "+borderAsCSS(border)+" }",m_styleSheet.cssRules().length());
+ }
+}
+
+void SetupDisplay::loadColumns( const QString &/*object*/, int cols )
+{
+ if ( m_currentItem ) {
+ m_currentItem->columns = cols;
+ }
+}
+
+void SetupDisplay::saveLayout( const QString &filename )
+{
+ QDomImplementation dom_imp;
+ QDomDocument doc = dom_imp.createDocument( QString::null, "krecipes-layout", dom_imp.createDocumentType( "krecipes-layout", QString::null, QString::null ) );
+
+ QDomElement layout_tag = doc.documentElement();
+ layout_tag.setAttribute( "version", 0.4 );
+ //layout_tag.setAttribute( "generator", QString("Krecipes v%1").arg(krecipes_version()) );
+ doc.appendChild( layout_tag );
+
+ for ( QMap<QString, KreDisplayItem*>::const_iterator it = node_item_map->begin(); it != node_item_map->end(); ++it ) {
+ QDomElement base_tag = doc.createElement( it.key() );
+ layout_tag.appendChild( base_tag );
+
+ int properties = (*box_properties)[it.data()];
+ if ( properties & BackgroundColor ) {
+ QDomElement backgroundcolor_tag = doc.createElement( "background-color" );
+ backgroundcolor_tag.appendChild( doc.createTextNode( it.data()->backgroundColor.name() ) );
+ base_tag.appendChild( backgroundcolor_tag );
+ }
+
+ if ( properties & TextColor ) {
+ QDomElement textcolor_tag = doc.createElement( "text-color" );
+ textcolor_tag.appendChild( doc.createTextNode( it.data()->textColor.name() ) );
+ base_tag.appendChild( textcolor_tag );
+ }
+
+ if ( properties & Font ) {
+ QDomElement font_tag = doc.createElement( "font" );
+ font_tag.appendChild( doc.createTextNode( it.data()->font.toString() ) );
+ base_tag.appendChild( font_tag );
+ }
+
+ if ( properties & Visibility ) {
+ QDomElement visibility_tag = doc.createElement( "visible" );
+ visibility_tag.appendChild( doc.createTextNode( (it.data()->show) ? "true" : "false" ) );
+ base_tag.appendChild( visibility_tag );
+ }
+
+ if ( properties & Alignment ) {
+ QDomElement alignment_tag = doc.createElement( "alignment" );
+ alignment_tag.appendChild( doc.createTextNode( QString::number( it.data()->alignment ) ) );
+ base_tag.appendChild( alignment_tag );
+ }
+
+ if ( properties & Border ) {
+ QDomElement border_tag = doc.createElement( "border" );
+ border_tag.setAttribute( "width", it.data()->border.width );
+ border_tag.setAttribute( "style", it.data()->border.style );
+ border_tag.setAttribute( "color", it.data()->border.color.name() );
+ base_tag.appendChild( border_tag );
+ }
+
+ if ( properties & Columns ) {
+ QDomElement columns_tag = doc.createElement( "columns" );
+ columns_tag.appendChild( doc.createTextNode( QString::number( it.data()->columns ) ) );
+ base_tag.appendChild( columns_tag );
+ }
+ }
+
+ QFile out_file( filename );
+ if ( out_file.open( IO_WriteOnly ) ) {
+ has_changes = false;
+
+ QTextStream stream( &out_file );
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" << doc.toString();
+ }
+ else
+ kdDebug() << "Error: Unable to write to file " << filename << endl;
+}
+
+void SetupDisplay::begin(const KURL &url, int xOffset, int yOffset)
+{
+ kdDebug()<<"begin"<<endl;
+ KHTMLPart::begin(url,xOffset,yOffset);
+ kdDebug()<<"end"<<endl;
+
+ DOM::Document doc = document();
+ DOM::DOMImplementation impl = doc.implementation();
+ kdDebug() << "(1) document: " << document().handle() << endl;
+ #if 0
+ if ( !impl.isNull() ) {
+ //m_styleSheet = impl.createCSSStyleSheet("-krecipes","screen");
+ //m_styleSheet = DOM::CSSStyleSheet();
+ //doc.addStyleSheet(m_styleSheet);
+ //applyStylesheet();
+ }
+ #endif
+}
+
+void SetupDisplay::nodeClicked(const QString &/*url*/,const QPoint &point)
+{
+ DOM::Node node = nodeUnderMouse();
+ DOM::Element element;
+ if ( node.nodeType() != DOM::Node::ELEMENT_NODE ) {
+ kdDebug()<<"not an element"<<endl;
+ element = (DOM::Element)node.parentNode();
+ }
+ else
+ element = (DOM::Element)node;
+
+ while ( !element.parentNode().isNull() ) {
+ if ( element.hasAttribute("class") ) {
+ QString id = element.getAttribute("class").string();
+ if ( node_item_map->keys().contains(id) )
+ break;
+ }
+
+ element = (DOM::Element)element.parentNode();
+ }
+
+ m_currNodeId = element.getAttribute("class").string();
+ if ( m_currNodeId.isEmpty() ) {
+ kdDebug()<<"Code error: unable to determine class of selected element"<<endl;
+ return;
+ }
+
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+
+ delete popup;
+ popup = new KPopupMenu( view() );
+ popup->insertTitle( item->name );
+
+ unsigned int properties = 0;
+ for ( PropertiesMap::const_iterator it = box_properties->begin(); it != box_properties->end(); ++it ) {
+ if ( it.key()->nodeId == m_currNodeId ) {
+ properties = it.data();
+ break;
+ }
+ }
+
+ KIconLoader il;
+
+ if ( properties & BackgroundColor )
+ popup->insertItem( i18n( "Background Color..." ), this, SLOT( setBackgroundColor() ) );
+
+ if ( properties & TextColor )
+ popup->insertItem( i18n( "Text Color..." ), this, SLOT( setTextColor() ) );
+
+ if ( properties & Font )
+ popup->insertItem( il.loadIconSet( "text", KIcon::Small, 16 ), i18n( "Font..." ), this, SLOT( setFont() ) );
+
+ if ( properties & Visibility ) {
+ int id = popup->insertItem( i18n( "Show" ), this, SLOT( setShown( int ) ) );
+ popup->setItemChecked( id, item->show );
+ }
+
+ if ( properties & Alignment ) {
+ QPopupMenu * sub_popup = new QPopupMenu( popup );
+
+ QActionGroup *alignment_actions = new QActionGroup( this );
+ alignment_actions->setExclusive( true );
+
+ QAction *c_action = new QAction( i18n( "Center" ), i18n( "Center" ), 0, alignment_actions, 0, true );
+ QAction *l_action = new QAction( i18n( "Left" ), i18n( "Left" ), 0, alignment_actions, 0, true );
+ QAction *r_action = new QAction( i18n( "Right" ), i18n( "Right" ), 0, alignment_actions, 0, true );
+
+ int align = item->alignment;
+ if ( align & Qt::AlignHCenter )
+ c_action->setOn(true);
+ if ( align & Qt::AlignLeft )
+ l_action->setOn(true);
+ if ( align & Qt::AlignRight )
+ r_action->setOn(true);
+
+ connect( alignment_actions, SIGNAL( selected( QAction* ) ), SLOT( setAlignment( QAction* ) ) );
+
+ popup->insertItem( i18n( "Alignment" ), sub_popup );
+
+ alignment_actions->addTo( sub_popup );
+ }
+
+ if ( properties & Border )
+ popup->insertItem( i18n( "Border..." ), this, SLOT( setBorder() ) );
+
+ if ( properties & Columns )
+ popup->insertItem( i18n( "Columns..." ), this, SLOT( setColumns() ) );
+
+ popup->popup( point );
+}
+
+void SetupDisplay::applyStylesheet()
+{
+ loadTemplate( m_activeTemplate );
+ if ( !document().isNull() && !m_styleSheet.isNull() ) {
+ //document().removeStyleSheet(m_styleSheet);
+ //document().addStyleSheet(m_styleSheet);
+ }
+}
+
+void SetupDisplay::setBackgroundColor()
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+ if ( KColorDialog::getColor( item->backgroundColor, view() ) == QDialog::Accepted ) {
+ m_currentItem = item;
+ loadBackgroundColor(m_currNodeId,item->backgroundColor);
+ m_currentItem = 0;
+
+ applyStylesheet();
+ has_changes = true;
+ }
+}
+
+void SetupDisplay::setBorder()
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+ BorderDialog borderDialog( item->border, view() );
+ if ( borderDialog.exec() == QDialog::Accepted ) {
+ m_currentItem = item;
+ loadBorder( m_currNodeId, borderDialog.border() );
+ m_currentItem = 0;
+
+ applyStylesheet();
+ has_changes = true;
+ }
+}
+
+void SetupDisplay::setColumns()
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+ int cols = QInputDialog::getInteger( QString::null, i18n("Select the number of columns to use:"), item->columns, 1, 100, 1, 0, view() );
+ if ( cols > 0 ) {
+ m_currentItem = item;
+ loadColumns( m_currNodeId, cols );
+ m_currentItem = 0;
+
+ loadTemplate( m_activeTemplate );
+ has_changes = true;
+ }
+}
+
+void SetupDisplay::setTextColor()
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+ if ( KColorDialog::getColor( item->textColor, view() ) == QDialog::Accepted ) {
+ m_currentItem = item;
+ loadTextColor(m_currNodeId,item->textColor);
+ m_currentItem = 0;
+
+ applyStylesheet();
+ has_changes = true;
+ }
+}
+
+void SetupDisplay::setShown( int id )
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+ emit itemVisibilityChanged( item, !popup->isItemChecked( id ) );
+
+ m_currentItem = item;
+ loadVisibility(m_currNodeId,!popup->isItemChecked( id ));
+ m_currentItem = 0;
+
+ applyStylesheet();
+ has_changes = true;
+}
+
+void SetupDisplay::setFont()
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+ if ( KFontDialog::getFont( item->font, false, view() ) == QDialog::Accepted ) {
+ m_currentItem = item;
+ loadFont(m_currNodeId,item->font);
+ m_currentItem = 0;
+
+ applyStylesheet();
+ has_changes = true;
+ }
+}
+
+void SetupDisplay::setAlignment( QAction *action )
+{
+ KreDisplayItem *item = *node_item_map->find( m_currNodeId );
+
+ //TODO: isn't there a simpler way to do this...
+ //preserve non-horizontal alignment flags
+ if ( item->alignment & Qt::AlignRight )
+ item->alignment ^= Qt::AlignRight;
+ if ( item->alignment & Qt::AlignHCenter )
+ item->alignment ^= Qt::AlignHCenter;
+ if ( item->alignment & Qt::AlignLeft )
+ item->alignment ^= Qt::AlignLeft;
+
+ if ( action->text() == i18n( "Center" ) )
+ item->alignment |= Qt::AlignHCenter;
+ else if ( action->text() == i18n( "Left" ) )
+ item->alignment |= Qt::AlignLeft;
+ else if ( action->text() == i18n( "Right" ) )
+ item->alignment |= Qt::AlignRight;
+
+ m_currentItem = item;
+ loadAlignment(m_currNodeId,item->alignment);
+ m_currentItem = 0;
+
+ applyStylesheet();
+ has_changes = true;
+}
+
+void SetupDisplay::setItemShown( KreDisplayItem *item, bool visible )
+{
+ item->show = visible;
+
+ m_styleSheet.insertRule("."+item->nodeId+" { visibility:"+(item->show?"visible":"hidden")+" }",m_styleSheet.cssRules().length());
+ applyStylesheet();
+
+ has_changes = true;
+}
+
+void SetupDisplay::changeMade( void )
+{
+ has_changes = true;
+}
+
+#include "setupdisplay.moc"
diff --git a/krecipes/src/dialogs/setupdisplay.h b/krecipes/src/dialogs/setupdisplay.h
new file mode 100644
index 0000000..d0370ed
--- /dev/null
+++ b/krecipes/src/dialogs/setupdisplay.h
@@ -0,0 +1,148 @@
+/***************************************************************************
+* Copyright (C) 2003 by Jason Kivlighn *
+* (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SETUPDISPLAY_H
+#define SETUPDISPLAY_H
+
+#include <khtml_part.h>
+#include <dom/html_element.h>
+#include <dom/css_stylesheet.h>
+
+#include <qdom.h>
+#include <qwidget.h>
+#include <qmap.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/kreborder.h"
+#include "klomanager.h"
+
+#include <math.h>
+
+class KPopupMenu;
+
+class QAction;
+class QLabel;
+class QWidget;
+
+class StyleSheet;
+
+class KreDisplayItem
+{
+public:
+ KreDisplayItem( const QString &id, const QString &name );
+
+ void clear();
+
+ QString nodeId;
+ QString name;
+ KreBorder border;
+ int alignment;
+ int columns;
+ bool show;
+ QColor backgroundColor;
+ QColor textColor;
+ QFont font;
+};
+
+typedef QMap< KreDisplayItem*, unsigned int > PropertiesMap;
+
+/** @brief A KHTMLPart for editing specific CSS properties
+ *
+ * Set up the items of a recipe for display.
+ *
+ * @author Jason Kivlighn
+ */
+class SetupDisplay : public KHTMLPart, protected KLOManager
+{
+ Q_OBJECT
+
+public:
+ SetupDisplay( const Recipe &, QWidget *parent );
+ ~SetupDisplay();
+
+ enum Properties { None = 0, BackgroundColor = 1, TextColor = 2, Font = 4, Visibility = 8, Alignment = 32, Columns = 64, Border = 128 };
+
+ void saveLayout( const QString & );
+ void loadLayout( const QString & );
+ void loadTemplate( const QString &filename );
+
+ bool hasChanges() const
+ {
+ return has_changes;
+ }
+
+ void setItemShown( KreDisplayItem *item, bool visible );
+
+ const PropertiesMap properties() const
+ {
+ return * box_properties;
+ }
+
+ void reload();
+
+signals:
+ void itemVisibilityChanged( KreDisplayItem *, bool );
+
+protected:
+ virtual void begin (const KURL &url=KURL(), int xOffset=0, int yOffset=0);
+
+ virtual void loadBackgroundColor( const QString &obj, const QColor& );
+ virtual void loadFont( const QString &obj, const QFont& );
+ virtual void loadTextColor( const QString &obj, const QColor& );
+ virtual void loadVisibility( const QString &obj, bool );
+ virtual void loadAlignment( const QString &obj, int );
+ virtual void loadBorder( const QString &obj, const KreBorder& );
+ virtual void loadColumns( const QString &obj, int );
+
+ virtual void beginObject( const QString &obj );
+ virtual void endObject();
+
+protected slots:
+ void nodeClicked(const QString &url,const QPoint &point);
+ void changeMade();
+
+ //slots to set properties of item boxes
+ void setBackgroundColor();
+ void setBorder();
+ void setColumns();
+ void setTextColor();
+ void setFont();
+ void setShown( int id );
+ void setAlignment( QAction * );
+
+private:
+ PropertiesMap *box_properties;
+ QMap<QString, KreDisplayItem*> *node_item_map;
+
+ bool has_changes;
+
+ // Methods
+ void applyStylesheet();
+ void loadPageLayout( const QDomElement &tag );
+ void loadHTMLView( const QString &templateFile = QString::null, const QString &styleFile = QString::null );
+
+ void createItem( const QString &id, const QString &name, unsigned int properties );
+
+ //the name of the element under the mouse on a right-click
+ QString m_currNodeId;
+
+ //the item corresponding to the current point of processing the KLO
+ KreDisplayItem *m_currentItem;
+
+ KPopupMenu *popup;
+ DOM::CSSStyleSheet m_styleSheet;
+ Recipe m_sample;
+
+ QString m_activeTemplate;
+ QString m_activeStyle;
+};
+
+#endif //SETUPDISPLAY_H
+
diff --git a/krecipes/src/dialogs/shoppinglistdialog.cpp b/krecipes/src/dialogs/shoppinglistdialog.cpp
new file mode 100644
index 0000000..1963cb2
--- /dev/null
+++ b/krecipes/src/dialogs/shoppinglistdialog.cpp
@@ -0,0 +1,267 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "shoppinglistdialog.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kcursor.h>
+#include <kdialog.h>
+#include <kglobal.h>
+#include <kapplication.h>
+
+#include "backends/recipedb.h"
+#include "refineshoppinglistdialog.h"
+#include "datablocks/recipelist.h"
+#include "widgets/recipelistview.h"
+#include "recipefilter.h"
+#include "recipeactionshandler.h"
+
+/** A simple listview to accept dropping a RecipeItemDrag */
+class ShoppingListView : public KListView
+{
+public:
+ ShoppingListView( QWidget *parent ) : KListView( parent )
+ {}
+
+protected:
+ bool acceptDrag( QDropEvent *event ) const
+ {
+ return RecipeItemDrag::canDecode( event );
+ }
+
+ QDragObject *dragObject()
+ {
+ RecipeListItem * item = dynamic_cast<RecipeListItem*>( selectedItem() );
+ if ( item != 0 ) {
+
+ RecipeItemDrag * obj = new RecipeItemDrag( item, this, "Recipe drag item" );
+ /*const QPixmap *pm = item->pixmap(0);
+ if( pm )
+ obj->setPixmap( *pm );*/
+ return obj;
+ }
+ return 0;
+ }
+};
+
+
+ShoppingListDialog::ShoppingListDialog( QWidget *parent, RecipeDB *db ) : QWidget( parent )
+{
+ // Store pointer to database
+ database = db;
+
+ // Design dialog
+ layout = new QGridLayout( this, 2, 2, KDialog::marginHint(), KDialog::spacingHint() );
+
+ recipeListView = new KreListView ( this, i18n( "Full recipe list" ), true, 1 );
+ layout->addWidget( recipeListView, 0, 0 );
+ listview = new RecipeListView( recipeListView, database );
+ listview->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::MinimumExpanding );
+ listview->setDragEnabled( true );
+ listview->setAcceptDrops( true );
+ listview->setDropVisualizer( false );
+ connect( recipeListView, SIGNAL( textChanged(const QString&) ), SLOT( ensurePopulated() ) );
+ connect( listview, SIGNAL( dropped( KListView*, QDropEvent*, QListViewItem* ) ),
+ this, SLOT( slotDropped( KListView*, QDropEvent*, QListViewItem* ) ) );
+ recipeListView->setListView( listview );
+ recipeListView->setCustomFilter( new RecipeFilter( recipeListView->listView() ), SLOT( filter( const QString & ) ) );
+ recipeListView->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+
+ QBoxLayout* vboxl = new QVBoxLayout( KDialog::spacingHint() );
+ KIconLoader il;
+ addRecipeButton = new QPushButton( this );
+ addRecipeButton->setIconSet( il.loadIconSet( "forward", KIcon::Small ) );
+ addRecipeButton->setFixedSize( QSize( 32, 32 ) );
+ addRecipeButton->setFlat( true );
+ vboxl->addWidget( addRecipeButton );
+
+ removeRecipeButton = new QPushButton( this );
+ removeRecipeButton->setIconSet( il.loadIconSet( "back", KIcon::Small ) );
+ removeRecipeButton->setFixedSize( QSize( 32, 32 ) );
+ removeRecipeButton->setFlat( true );
+ vboxl->addWidget( removeRecipeButton );
+ vboxl->addStretch();
+
+ layout->addItem( vboxl, 0, 1 );
+
+ shopRecipeListView = new KreListView ( this, i18n("Shopping List") );
+ ShoppingListView *slistview = new ShoppingListView( shopRecipeListView );
+ slistview->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::MinimumExpanding );
+ slistview->setDragEnabled( true );
+ slistview->setAcceptDrops( true );
+ slistview->setDropVisualizer( false );
+ connect( slistview, SIGNAL( dropped( KListView*, QDropEvent*, QListViewItem* ) ),
+ this, SLOT( slotDropped( KListView*, QDropEvent*, QListViewItem* ) ) );
+ shopRecipeListView->setListView( slistview );
+ layout->addWidget( shopRecipeListView, 0, 2 );
+
+ shopRecipeListView->listView() ->addColumn( i18n( "Recipe Title" ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ shopRecipeListView->listView() ->addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ shopRecipeListView->listView() ->setSorting( -1 );
+ shopRecipeListView->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ shopRecipeListView->listView() ->setAllColumnsShowFocus( true );
+
+ buttonBar = new QHBox( this, "buttonBar" );
+ layout->addMultiCellWidget( buttonBar, 1, 1, 0, 2 );
+
+ layout->setColStretch( 0, 1 );
+ layout->setColStretch( 1, 0 );
+ layout->setColStretch( 2, 1 );
+
+ okButton = new QPushButton( buttonBar, "okButton" );
+ okButton->setText( i18n( "&OK" ) );
+ QPixmap pm = il.loadIcon( "ok", KIcon::NoGroup, 16 );
+ okButton->setIconSet( pm );
+
+ //buttonBar->layout()->addItem( new QSpacerItem( 10,10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
+
+ clearButton = new QPushButton( buttonBar, "clearButton" );
+ clearButton->setText( i18n( "Clear" ) );
+ pm = il.loadIcon( "editclear", KIcon::NoGroup, 16 );
+ clearButton->setIconSet( pm );
+
+ //Takes care of all recipe actions and provides a popup menu to 'recipeListView'
+ actionHandler = new RecipeActionsHandler( recipeListView->listView(), database, RecipeActionsHandler::ExpandAll | RecipeActionsHandler::CollapseAll );
+
+ // Connect signals & slots
+ connect( addRecipeButton, SIGNAL( clicked() ), this, SLOT( addRecipe() ) );
+ connect( removeRecipeButton, SIGNAL( clicked() ), this, SLOT( removeRecipe() ) );
+ connect( okButton, SIGNAL( clicked() ), this, SLOT( showShoppingList() ) );
+ connect( clearButton, SIGNAL( clicked() ), this, SLOT( clear() ) );
+}
+
+ShoppingListDialog::~ShoppingListDialog()
+{}
+
+void ShoppingListDialog::ensurePopulated()
+{
+ listview->populateAll();
+}
+
+void ShoppingListDialog::createShopping( const RecipeList &rlist )
+{
+ clear();
+ RecipeList::const_iterator it;
+ for ( it = rlist.begin(); it != rlist.end(); ++it ) {
+ new RecipeListItem( shopRecipeListView->listView(), shopRecipeListView->listView() ->lastItem(), *it );
+ }
+}
+
+void ShoppingListDialog::reloadRecipeList( ReloadFlags flag )
+{
+ ( ( RecipeListView* ) recipeListView->listView() ) ->reload( flag );
+}
+
+void ShoppingListDialog::reload( ReloadFlags flag )
+{
+ reloadRecipeList ( flag ); // Missing: check if there's non-existing recipes in the list now, and if so, delete.
+}
+
+void ShoppingListDialog::addRecipe( void )
+{
+ QPtrList<QListViewItem> items = recipeListView->listView()->selectedItems();
+
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ addRecipe( item );
+ ++it;
+ }
+}
+
+void ShoppingListDialog::addRecipe( QListViewItem *item )
+{
+ if ( item ) {
+ if ( item->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) item;
+
+ Recipe r;
+ r.title = recipe_it->title();
+ r.recipeID = recipe_it->recipeID();
+ ( void ) new RecipeListItem( shopRecipeListView->listView(), r );
+ }
+ }
+}
+
+void ShoppingListDialog::removeRecipe( void )
+{
+ QListViewItem * it;
+ it = shopRecipeListView->listView() ->selectedItem();
+ if ( it )
+ delete it;
+}
+
+void ShoppingListDialog::showShoppingList( void )
+{
+ // Store the recipe list in ElementList object first
+ ElementList recipeList;
+ RecipeListItem *it;
+ for ( it = ( RecipeListItem* ) shopRecipeListView->listView() ->firstChild();it;it = ( RecipeListItem* ) it->nextSibling() ) {
+ Element newEl;
+ newEl.id = it->recipeID();
+ newEl.name = it->title(); // Storing the title is not necessary, but do it just in case it's used later on
+ recipeList.append( newEl );
+ }
+
+ RefineShoppingListDialog refineDialog( this, database, recipeList );
+ refineDialog.exec();
+}
+
+void ShoppingListDialog::addRecipeToShoppingList( int recipeID )
+{
+ Recipe r;
+ r.title = database->recipeTitle( recipeID );
+ r.recipeID = recipeID;
+
+ new RecipeListItem( shopRecipeListView->listView(), r );
+}
+
+void ShoppingListDialog::clear()
+{
+ shopRecipeListView->listView() ->clear();
+}
+
+void ShoppingListDialog::slotDropped( KListView *list, QDropEvent *e, QListViewItem * /*after*/ )
+{
+ Recipe r;
+ RecipeListItem *item = new RecipeListItem( recipeListView->listView(), r ); // needs parent, use this temporarily
+ if ( !RecipeItemDrag::decode( e, *item ) ) {
+ delete item;
+ return ;
+ }
+
+ if ( list == shopRecipeListView->listView() ) {
+ addRecipe( item );
+ }
+ //find and delete the item if we just dropped onto the recipe list from the shopping list
+ else if ( list == recipeListView->listView() && e->source() == shopRecipeListView->listView() ) {
+ QListViewItemIterator list_it = QListViewItemIterator( shopRecipeListView->listView() );
+ while ( list_it.current() ) {
+ if ( ( ( RecipeListItem* ) list_it.current() ) ->recipeID() == item->recipeID() ) {
+ delete list_it.current();
+ break;
+ }
+ list_it++;
+ }
+ }
+
+ delete item;
+ item = 0; // not needed anymore
+}
+
+#include "shoppinglistdialog.moc"
diff --git a/krecipes/src/dialogs/shoppinglistdialog.h b/krecipes/src/dialogs/shoppinglistdialog.h
new file mode 100644
index 0000000..2c25ae9
--- /dev/null
+++ b/krecipes/src/dialogs/shoppinglistdialog.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SHOPPINGLISTDIALOG_H
+#define SHOPPINGLISTDIALOG_H
+
+#include <qhbox.h>
+#include <qlayout.h>
+#include <kpushbutton.h>
+#include <kiconloader.h>
+
+#include "widgets/krelistview.h"
+#include "widgets/dblistviewbase.h"
+
+class RecipeDB;
+class RecipeList;
+class ShoppingListViewDialog;
+class RecipeActionsHandler;
+class RecipeListView;
+
+/**
+@author Unai Garro
+*/
+
+class ShoppingListDialog: public QWidget
+{
+ Q_OBJECT
+public:
+
+ ShoppingListDialog( QWidget *parent, RecipeDB *db );
+ ~ShoppingListDialog();
+ void reload( ReloadFlags flag = Load );
+ void createShopping( const RecipeList &rlist );
+
+private:
+
+ // Internal variables
+ RecipeDB *database;
+ // Internal Methods
+ void reloadRecipeList( ReloadFlags flag = Load );
+ void addRecipe( QListViewItem *item );
+ // Widgets
+ QGridLayout* layout;
+ QPushButton* addRecipeButton;
+ QPushButton* removeRecipeButton;
+ KreListView* recipeListView;
+ KreListView* shopRecipeListView;
+ QHBox *buttonBar;
+ QPushButton* okButton;
+ QPushButton* clearButton;
+ ShoppingListViewDialog *shoppingListDisplay;
+ RecipeActionsHandler *actionHandler;
+ RecipeListView *listview;
+
+public slots:
+ void addRecipeToShoppingList( int recipeID ); // Called from inputdialog-> through krecipesview
+
+private slots:
+ void addRecipe( void );
+ void removeRecipe( void );
+ void showShoppingList( void );
+ void clear( void );
+ void slotDropped( KListView *list, QDropEvent *e, QListViewItem *after );
+ void ensurePopulated();
+};
+
+#endif
diff --git a/krecipes/src/dialogs/shoppinglistviewdialog.cpp b/krecipes/src/dialogs/shoppinglistviewdialog.cpp
new file mode 100644
index 0000000..24e04d2
--- /dev/null
+++ b/krecipes/src/dialogs/shoppinglistviewdialog.cpp
@@ -0,0 +1,102 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "shoppinglistviewdialog.h"
+#include "datablocks/ingredientlist.h"
+#include "datablocks/mixednumber.h"
+
+#include <qpushbutton.h>
+
+#include <klocale.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kiconloader.h>
+
+ShoppingListViewDialog::ShoppingListViewDialog( QWidget *parent, const IngredientList &ingredientList )
+ : KDialogBase( parent, "shoppingviewdialog", true, QString::null,
+ KDialogBase::Close | KDialogBase::User1, KDialogBase::Close,
+ false, KStdGuiItem::print() )
+{
+ // Design dialog
+ QVBox *page = makeVBoxMainWidget();
+
+ shoppingListView = new KHTMLPart( page );
+
+ setInitialSize( QSize(350, 450) );
+
+ connect ( this, SIGNAL( user1Clicked() ), this, SLOT( print() ) );
+ connect ( this, SIGNAL( closeClicked() ), this, SLOT( accept() ) );
+
+ //---------- Sort the list --------
+ IngredientList list_copy = ingredientList;
+ qHeapSort( list_copy );
+
+ //---------- Load the list --------
+ display( list_copy );
+}
+
+
+ShoppingListViewDialog::~ShoppingListViewDialog()
+{}
+
+void ShoppingListViewDialog::display( const IngredientList &ingredientList )
+{
+ QString recipeHTML;
+
+ // Create HTML Code
+
+ // Headers
+ recipeHTML = QString( "<html><head><title>%1</title></head><body>" ).arg( i18n( "Shopping List" ) );
+ recipeHTML += "<center><div STYLE=\"width: 95%\">";
+ recipeHTML += QString( "<center><h1>%1</h1></center>" ).arg( i18n( "Shopping List" ) );
+
+ // Ingredient List
+
+ recipeHTML += "<div STYLE=\"border:medium solid blue; width:95%\"><table cellspacing=0px width=100%><tbody>";
+ bool counter = true;
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Formatting" );
+
+ bool useAbbreviations = config->readBoolEntry("AbbreviateUnits");
+ bool useFraction = config->readBoolEntry( "Fraction" );
+
+ for ( IngredientList::const_iterator ing_it = ingredientList.begin(); ing_it != ingredientList.end(); ++ing_it ) {
+ QString color = ( counter ) ? "#CBCEFF" : "#BFC2F0";
+ counter = !counter;
+
+ MixedNumber::Format number_format = ( useFraction ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+ QString amount_str = MixedNumber( ( *ing_it ).amount ).toString( number_format );
+
+ QString unit = ( *ing_it ).units.determineName( ( *ing_it ).amount + ( *ing_it ).amount_offset, useAbbreviations );
+
+ recipeHTML += QString( "<tr bgcolor=\"%1\"><td>- %2:</td><td>%3 %4</td></tr>" ).arg( color ).arg( ( *ing_it ).name ).arg( amount_str ).arg( unit );
+ }
+ recipeHTML += "</tbody></table></div>";
+ // Close
+ recipeHTML += "</div></center></body></html>";
+
+
+ // Display
+ shoppingListView->begin( KURL( "file:/tmp/" ) ); // Initialize to /tmp, where photos and logos are stored
+ shoppingListView->write( recipeHTML );
+ shoppingListView->end();
+
+
+}
+
+void ShoppingListViewDialog::print()
+{
+ shoppingListView->view() ->print();
+}
+
+#include "shoppinglistviewdialog.moc"
diff --git a/krecipes/src/dialogs/shoppinglistviewdialog.h b/krecipes/src/dialogs/shoppinglistviewdialog.h
new file mode 100644
index 0000000..4cfd4b6
--- /dev/null
+++ b/krecipes/src/dialogs/shoppinglistviewdialog.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SHOPPINGLISTVIEWDIALOG_H
+#define SHOPPINGLISTVIEWDIALOG_H
+
+#include <qlayout.h>
+#include <qvbox.h>
+#include <qwidget.h>
+
+#include <khtml_part.h>
+#include <khtmlview.h>
+#include <kdialogbase.h>
+
+class IngredientList;
+
+class ShoppingListViewDialog: public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ ShoppingListViewDialog( QWidget *parent, const IngredientList &ingredientList );
+ ~ShoppingListViewDialog();
+
+public slots:
+ void print();
+
+private:
+
+ // Widgets
+ KHTMLPart *shoppingListView;
+
+ // Internal Methods
+ void display( const IngredientList &ingredientList );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/similarcategoriesdialog.cpp b/krecipes/src/dialogs/similarcategoriesdialog.cpp
new file mode 100644
index 0000000..3d37113
--- /dev/null
+++ b/krecipes/src/dialogs/similarcategoriesdialog.cpp
@@ -0,0 +1,383 @@
+/***************************************************************************
+* Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "similarcategoriesdialog.h"
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <qcombobox.h>
+#include <qslider.h>
+#include <qlabel.h>
+#include <qheader.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+#include <klistview.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <klineedit.h>
+
+#include "widgets/categorycombobox.h"
+#include "backends/recipedb.h"
+
+SimilarCategoriesDialog::SimilarCategoriesDialog( ElementList &list, QWidget* parent )
+ : QDialog( parent, "SimilarCategoriesDialog", true ),
+ m_elementList(list)
+{
+ SimilarCategoriesDialogLayout = new QVBoxLayout( this, 11, 6, "SimilarCategoriesDialogLayout");
+
+ layout6 = new QHBoxLayout( 0, 0, 6, "layout6");
+
+ layout4 = new QGridLayout( 0, 1, 1, 0, 6, "layout4");
+
+ categoriesBox = new KLineEdit( this );
+
+ layout4->addWidget( categoriesBox, 0, 1 );
+
+ thresholdSlider = new QSlider( this, "thresholdSlider" );
+ thresholdSlider->setValue( 40 );
+ thresholdSlider->setOrientation( QSlider::Horizontal );
+
+ layout4->addWidget( thresholdSlider, 1, 1 );
+
+ thresholdLabel = new QLabel( this, "thresholdLabel" );
+
+ layout4->addWidget( thresholdLabel, 1, 0 );
+
+ categoryLabel = new QLabel( this, "categoryLabel" );
+
+ layout4->addWidget( categoryLabel, 0, 0 );
+ layout6->addLayout( layout4 );
+
+ layout5 = new QVBoxLayout( 0, 0, 6, "layout5");
+
+ searchButton = new QPushButton( this, "searchButton" );
+ layout5->addWidget( searchButton );
+ spacer4 = new QSpacerItem( 20, 51, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ layout5->addItem( spacer4 );
+ layout6->addLayout( layout5 );
+ SimilarCategoriesDialogLayout->addLayout( layout6 );
+
+ layout9 = new QHBoxLayout( 0, 0, 6, "layout9");
+
+ layout8 = new QVBoxLayout( 0, 0, 6, "layout8");
+
+ allLabel = new QLabel( this, "allLabel" );
+ layout8->addWidget( allLabel );
+
+ allListView = new KListView( this, "allListView" );
+ allListView->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)7, (QSizePolicy::SizeType)7, 0, 1, allListView->sizePolicy().hasHeightForWidth() ) );
+ layout8->addWidget( allListView );
+ layout9->addLayout( layout8 );
+
+ layout1 = new QVBoxLayout( 0, 0, 6, "layout1");
+
+ removeButton = new QPushButton( this, "removeButton" );
+ layout1->addWidget( removeButton );
+
+ addButton = new QPushButton( this, "addButton" );
+ layout1->addWidget( addButton );
+ spacer1 = new QSpacerItem( 20, 61, QSizePolicy::Minimum, QSizePolicy::Expanding );
+ layout1->addItem( spacer1 );
+ layout9->addLayout( layout1 );
+
+ layout7 = new QVBoxLayout( 0, 0, 6, "layout7");
+
+ toMergeLabel = new QLabel( this, "toMergeLabel" );
+ layout7->addWidget( toMergeLabel );
+
+ toMergeListView = new KListView( this, "toMergeListView" );
+ toMergeListView->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)7, (QSizePolicy::SizeType)7, 0, 1, toMergeListView->sizePolicy().hasHeightForWidth() ) );
+ layout7->addWidget( toMergeListView );
+ layout9->addLayout( layout7 );
+ SimilarCategoriesDialogLayout->addLayout( layout9 );
+
+ layout10 = new QHBoxLayout( 0, 0, 6, "layout10");
+ spacer2 = new QSpacerItem( 310, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
+ layout10->addItem( spacer2 );
+
+ mergeButton = new QPushButton( this, "mergeButton" );
+ layout10->addWidget( mergeButton );
+
+ cancelButton = new QPushButton( this, "cancelButton" );
+ layout10->addWidget( cancelButton );
+ SimilarCategoriesDialogLayout->addLayout( layout10 );
+ languageChange();
+ resize( QSize(573, 429).expandedTo(minimumSizeHint()) );
+ clearWState( WState_Polished );
+
+ connect( searchButton, SIGNAL(clicked()), this, SLOT(findMatches()) );
+ connect( mergeButton, SIGNAL(clicked()), this, SLOT(mergeMatches()) );
+ connect( cancelButton, SIGNAL(clicked()), this, SLOT(reject()) );
+ connect( addButton, SIGNAL(clicked()), this, SLOT(addCategory()) );
+ connect( removeButton, SIGNAL(clicked()), this, SLOT(removeCategory()) );
+}
+
+/*
+ * Destroys the object and frees any allocated resources
+ */
+SimilarCategoriesDialog::~SimilarCategoriesDialog()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+/*
+ * Sets the strings of the subwidgets using the current
+ * language.
+ */
+void SimilarCategoriesDialog::languageChange()
+{
+ setCaption( i18n( "Similar Categories" ) );
+ thresholdLabel->setText( i18n( "Threshold:" ) );
+ categoryLabel->setText( i18n( "Category:" ) );
+ searchButton->setText( i18n( "Search" ) );
+ allLabel->setText( i18n( "Similar Categories:" ) );
+ removeButton->setText( i18n( "<<" ) );
+ addButton->setText( i18n( ">>" ) );
+ toMergeLabel->setText( i18n( "Categories to Merge:" ) );
+ mergeButton->setText( i18n( "Merge" ) );
+ cancelButton->setText( i18n( "Cancel" ) );
+
+ allListView->addColumn( i18n( "Category" ) );
+ //allListView->addColumn( i18n( "Id" ) );
+ toMergeListView->addColumn( i18n( "Category" ) );
+ //toMergeListView->addColumn( i18n( "Id" ) );
+}
+
+/*****************************************************/
+/*Function prototypes and libraries needed to compile*/
+/*****************************************************/
+
+#include <stdlib.h>
+#include <malloc.h>
+#include <string.h>
+int levenshtein_distance(const char *s,const char*t);
+int minimum(int a,int b,int c);
+
+/****************************************/
+/*Implementation of Levenshtein distance*/
+/****************************************/
+
+int levenshtein_distance(const char *s,const char*t)
+/*Compute levenshtein distance between s and t*/
+{
+ //Step 1
+ int k,i,j,n,m,cost,*d,distance;
+ n=strlen(s);
+ m=strlen(t);
+ if(n!=0&&m!=0)
+ {
+ d=(int*)malloc((sizeof(int))*(m+1)*(n+1));
+ m++;
+ n++;
+ //Step 2
+ for(k=0;k<n;k++)
+ d[k]=k;
+ for(k=0;k<m;k++)
+ d[k*n]=k;
+ //Step 3 and 4
+ for(i=1;i<n;i++)
+ for(j=1;j<m;j++)
+ {
+ //Step 5
+ if(s[i-1]==t[j-1])
+ cost=0;
+ else
+ cost=1;
+ //Step 6
+ d[j*n+i]=minimum(d[(j-1)*n+i]+1,d[j*n+i-1]+1,d[(j-1)*n+i-1]+cost);
+ }
+ distance=d[n*m-1];
+ free(d);
+ return distance;
+ }
+ else
+ return -1; //a negative return value means that one or both strings are empty.
+}
+
+int minimum(int a,int b,int c)
+/*Gets the minimum of three values*/
+{
+ int min=a;
+ if(b<min)
+ min=b;
+ if(c<min)
+ min=c;
+ return min;
+}
+
+/** @return an array of adjacent letter pairs contained in the input string */
+QStringList letterPairs(const QString& str) {
+ int numPairs = str.length()-1;
+ QStringList pairs;
+ for (int i=0; i<numPairs; i++) {
+ pairs << str.mid(i,2);
+ }
+ return pairs;
+}
+
+/** @return an ArrayList of 2-character Strings. */
+QValueList<QStringList> wordLetterPairs(const QString &str) {
+ QValueList<QStringList> allPairs;
+ // Tokenize the string and put the tokens/words into an array
+ QStringList words = QStringList::split("\\s",str);
+ // For each word
+ for (uint w=0; w < words.count(); w++) {
+ // Find the pairs of characters
+ QStringList pairsInWord = letterPairs(words[w]);
+ for (uint p=0; p < pairsInWord.count(); p++) {
+ allPairs.append(pairsInWord[p]);
+ }
+ }
+ return allPairs;
+}
+
+/** @return lexical similarity value in the range [0,1] */
+double compareStrings(const QString &str1, const QString &str2) {
+ QValueList<QStringList> pairs1 = wordLetterPairs(str1.upper());
+ QValueList<QStringList> pairs2 = wordLetterPairs(str2.upper());
+ int intersection = 0;
+ int size_union = pairs1.count() + pairs2.count();
+ for (uint i=0; i<pairs1.count(); i++) {
+ QStringList pair1=pairs1[i];
+ for(uint j=0; j<pairs2.count(); j++) {
+ QStringList pair2=pairs2[j];
+ if (pair1 == pair2) {
+ intersection++;
+ pairs2.remove( pairs2.at(j) );
+ break;
+ }
+ }
+ }
+ return (2.0*intersection)/size_union;
+}
+
+
+
+#include <kdebug.h>
+
+#if 0
+void RecipeActionsHandler::mergeSimilar()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 1 )
+ KMessageBox::sorry( kapp->mainWidget(), i18n("Please select only one category."), QString::null );
+ else if ( items.count() == 1 && items.at(0)->rtti() == 1001 ) {
+ CategoryListItem * cat_it = ( CategoryListItem* ) items.at(0);
+ QString name = cat_it->categoryName();
+ const double max_allowed_distance = 0.60;
+ const int length = name.length();
+ ElementList categories;
+ database->loadCategories( &categories );
+
+ ElementList matches;
+ for ( ElementList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ #if 0
+ if ( levenshtein_distance(name.latin1(),(*it).name.latin1())/double(QMAX(length,(*it).name.length())) >= max_allowed_distance ) {
+ #else
+ if ( compareStrings(name,(*it).name) >= max_allowed_distance ) {
+ #endif
+ kdDebug()<<(*it).name<<" matches"<<endl;
+ if ( cat_it->categoryId() != (*it).id )
+ matches.append(*it);
+ }
+ }
+
+
+ for ( ElementList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ database->mergeCategories(cat_it->categoryId(),(*it).id);
+ }
+
+ }
+ else //either nothing was selected or a recipe was selected
+ KMessageBox::sorry( kapp->mainWidget(), i18n("No recipes selected."), i18n("Edit Recipe") );
+}
+#endif
+
+void SimilarCategoriesDialog::findMatches()
+{
+ allListView->clear();
+ toMergeListView->clear();
+
+ const double threshold = (100 - thresholdSlider->value())/100.0;
+ const QString name = categoriesBox->text();
+
+ for ( ElementList::const_iterator it = m_elementList.begin(); it != m_elementList.end(); ++it ) {
+ //kdDebug()<<(*it).name<<" (result/threshold): "<<compareStrings(name,(*it).name)<<"/"<<threshold<<endl;
+ #if 0
+ if ( levenshtein_distance(name.latin1(),(*it).name.latin1())/double(QMAX(length,(*it).name.length())) >= max_allowed_distance ) {
+ #else
+ if ( compareStrings(name,(*it).name) >= threshold ) {
+ #endif
+ kdDebug()<<(*it).name<<" matches"<<endl;
+ //if ( id != (*it).id ) {
+ (void) new QListViewItem(allListView,(*it).name,QString::number((*it).id));
+ (void) new QListViewItem(toMergeListView,(*it).name,QString::number((*it).id));
+ //}
+ }
+ }
+}
+
+void SimilarCategoriesDialog::mergeMatches()
+{
+ if ( !toMergeListView->firstChild() ) {
+ KMessageBox::sorry( this, i18n("No categories selected to merge."), QString::null );
+ return;
+ }
+
+ //const int id = categoriesBox->id(categoriesBox->currentItem());
+ //for ( QListViewItem *item = toMergeListView->firstChild(); item; item = item->nextSibling() ) {
+ // m_database->mergeCategories(id,item->text(1).toInt());
+ //}
+
+ allListView->clear();
+ //toMergeListView->clear();
+
+ QDialog::accept();
+}
+
+QValueList<int> SimilarCategoriesDialog::matches() const
+{
+ QValueList<int> ids;
+ for ( QListViewItem *item = toMergeListView->firstChild(); item; item = item->nextSibling() ) {
+ ids << item->text(1).toInt();
+ }
+
+ return ids;
+}
+
+QString SimilarCategoriesDialog::element() const
+{
+ return categoriesBox->text();
+}
+
+void SimilarCategoriesDialog::addCategory()
+{
+ QListViewItem *item = allListView->selectedItem();
+ if ( item )
+ {
+ //make sure it isn't already in the list
+ for ( QListViewItem *search_it = toMergeListView->firstChild(); search_it; search_it = search_it->nextSibling() ) {
+ if ( search_it->text(0) == item->text(0) )
+ return;
+ }
+
+ (void) new QListViewItem(toMergeListView,item->text(0),item->text(1));
+ }
+}
+
+void SimilarCategoriesDialog::removeCategory()
+{
+ QListViewItem *item = toMergeListView->selectedItem();
+ if ( item )
+ delete item;
+}
+
+#include "similarcategoriesdialog.moc"
diff --git a/krecipes/src/dialogs/similarcategoriesdialog.h b/krecipes/src/dialogs/similarcategoriesdialog.h
new file mode 100644
index 0000000..3fadc5d
--- /dev/null
+++ b/krecipes/src/dialogs/similarcategoriesdialog.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+* Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef SIMILARCATEGORIESDIALOG_H
+#define SIMILARCATEGORIESDIALOG_H
+
+#include <qvariant.h>
+#include <qdialog.h>
+#include <qvaluevector.h>
+
+#include "datablocks/elementlist.h"
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QSlider;
+class QLabel;
+class QPushButton;
+class QListViewItem;
+
+class KListView;
+class KLineEdit;
+
+class RecipeDB;
+
+class SimilarCategoriesDialog : public QDialog
+{
+Q_OBJECT
+
+public:
+ SimilarCategoriesDialog( ElementList &, QWidget* parent = 0 );
+ ~SimilarCategoriesDialog();
+
+ QValueList<int> matches() const;
+ QString element() const;
+
+ KLineEdit* categoriesBox;
+ QSlider* thresholdSlider;
+ QLabel* thresholdLabel;
+ QLabel* categoryLabel;
+ QPushButton* searchButton;
+ QLabel* allLabel;
+ KListView* allListView;
+ QPushButton* removeButton;
+ QPushButton* addButton;
+ QLabel* toMergeLabel;
+ KListView* toMergeListView;
+ QPushButton* mergeButton;
+ QPushButton* cancelButton;
+
+protected:
+ QVBoxLayout* SimilarCategoriesDialogLayout;
+ QHBoxLayout* layout6;
+ QGridLayout* layout4;
+ QVBoxLayout* layout5;
+ QSpacerItem* spacer4;
+ QHBoxLayout* layout9;
+ QVBoxLayout* layout8;
+ QVBoxLayout* layout1;
+ QSpacerItem* spacer1;
+ QVBoxLayout* layout7;
+ QHBoxLayout* layout10;
+ QSpacerItem* spacer2;
+
+protected slots:
+ virtual void languageChange();
+ void findMatches();
+ void mergeMatches();
+ void addCategory();
+ void removeCategory();
+
+private:
+ ElementList m_elementList;
+
+};
+
+#endif // SIMILARCATEGORIESDIALOG_H
diff --git a/krecipes/src/dialogs/unitsdialog.cpp b/krecipes/src/dialogs/unitsdialog.cpp
new file mode 100644
index 0000000..e60f72a
--- /dev/null
+++ b/krecipes/src/dialogs/unitsdialog.cpp
@@ -0,0 +1,211 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <qlayout.h>
+#include <qtabwidget.h>
+
+#include "unitsdialog.h"
+#include "createelementdialog.h"
+#include "dependanciesdialog.h"
+#include "backends/recipedb.h"
+#include "widgets/conversiontable.h"
+#include "widgets/unitlistview.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kdialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprogress.h>
+
+UnitsDialog::UnitsDialog( QWidget *parent, RecipeDB *db ) : QWidget( parent )
+{
+
+ // Store pointer to database
+ database = db;
+
+ // Design dialog
+ QHBoxLayout* page_layout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ QTabWidget *tabWidget = new QTabWidget( this );
+
+ QWidget *unitTab = new QWidget( tabWidget );
+ QHBoxLayout* layout = new QHBoxLayout( unitTab, KDialog::marginHint(), KDialog::spacingHint() );
+
+ unitListView = new StdUnitListView( unitTab, database, true );
+ layout->addWidget( unitListView );
+
+ QVBoxLayout* vboxl = new QVBoxLayout( KDialog::spacingHint() );
+ newUnitButton = new QPushButton( unitTab );
+ newUnitButton->setText( i18n( "Create ..." ) );
+ newUnitButton->setFlat( true );
+ vboxl->addWidget( newUnitButton );
+
+ removeUnitButton = new QPushButton( unitTab );
+ removeUnitButton->setText( i18n( "Delete" ) );
+ removeUnitButton->setFlat( true );
+ vboxl->addWidget( removeUnitButton );
+ vboxl->addStretch();
+ layout->addLayout( vboxl );
+
+ tabWidget->insertTab( unitTab, i18n( "Units" ) );
+
+ massConversionTable = new ConversionTable( tabWidget, 1, 1 );
+ tabWidget->insertTab( massConversionTable, i18n( "Mass Conversions" ) );
+
+ volumeConversionTable = new ConversionTable( tabWidget, 1, 1 );
+ tabWidget->insertTab( volumeConversionTable, i18n( "Volume Conversions" ) );
+
+ page_layout->addWidget( tabWidget );
+
+ // Connect signals & slots
+ connect( newUnitButton, SIGNAL( clicked() ), unitListView, SLOT( createNew() ) );
+ connect( removeUnitButton, SIGNAL( clicked() ), unitListView, SLOT( remove() ) );
+ connect( massConversionTable, SIGNAL( ratioChanged( int, int, double ) ), this, SLOT( saveRatio( int, int, double ) ) );
+ connect( massConversionTable, SIGNAL( ratioRemoved( int, int ) ), this, SLOT( removeRatio( int, int ) ) );
+ connect( volumeConversionTable, SIGNAL( ratioChanged( int, int, double ) ), this, SLOT( saveRatio( int, int, double ) ) );
+ connect( volumeConversionTable, SIGNAL( ratioRemoved( int, int ) ), this, SLOT( removeRatio( int, int ) ) );
+
+ //TODO: I'm too lazy right now, so do a complete reload to keep in sync with db
+ connect( database, SIGNAL( unitCreated( const Unit& ) ), this, SLOT( loadConversionTables() ) );
+ connect( database, SIGNAL( unitRemoved( int ) ), this, SLOT( loadConversionTables() ) );
+
+ //this is for the above TODO, but it still has some bugs to be worked out
+ //connect(database,SIGNAL(unitCreated(const Element&)),conversionTable,SLOT(unitCreated(const Element&)));
+ //connect(database,SIGNAL(unitRemoved(int)),conversionTable,SLOT(unitRemoved(int)));
+
+ //Populate data into the table
+ loadConversionTables();
+
+ //FIXME: We've got some sort of build issue... we get undefined references to CreateElementDialog without this dummy code here
+ CreateElementDialog d( this, "" );
+}
+
+UnitsDialog::~UnitsDialog()
+{}
+
+void UnitsDialog::reload( ReloadFlags flag )
+{
+ unitListView->reload( flag );
+ loadConversionTables();
+}
+
+void UnitsDialog::loadConversionTables( void )
+{
+ loadConversionTable( massConversionTable, Unit::Mass );
+ loadConversionTable( volumeConversionTable, Unit::Volume );
+}
+
+void UnitsDialog::loadConversionTable( ConversionTable *table, Unit::Type type )
+{
+ UnitList unitList;
+ database->loadUnits( &unitList, type );
+
+ QStringList unitNames;
+ IDList unitIDs; // We need to store these in the table, so rows and cols are identified by unitID, not name.
+ table->clear();
+ for ( UnitList::const_iterator unit_it = unitList.begin(); unit_it != unitList.end(); ++unit_it ) {
+ unitNames.append( ( *unit_it ).name );
+ unitIDs.append( ( *unit_it ).id ); // append the element
+ }
+
+ // Resize the table
+ table->resize( unitNames.count(), unitNames.count() );
+
+ // Set the table labels, and id's
+ table->setRowLabels( unitNames );
+ table->setColumnLabels( unitNames );
+ table->setUnitIDs( unitIDs );
+
+
+ // Load and Populate the data into the table
+ UnitRatioList ratioList;
+ database->loadUnitRatios( &ratioList, type );
+ for ( UnitRatioList::const_iterator ratio_it = ratioList.begin(); ratio_it != ratioList.end(); ++ratio_it ) {
+ table->setRatio( ( *ratio_it ).uID1, ( *ratio_it ).uID2, ( *ratio_it ).ratio );
+ }
+}
+
+void UnitsDialog::saveRatio( int r, int c, double value )
+{
+ ConversionTable *conversionTable = (ConversionTable*)sender();
+ UnitRatio ratio;
+
+ ratio.uID1 = conversionTable->getUnitID( r );
+ ratio.uID2 = conversionTable->getUnitID( c );
+ ratio.ratio = value;
+ database->saveUnitRatio( &ratio );
+
+ UnitRatio reverse_ratio;
+ reverse_ratio.uID1 = ratio.uID2;
+ reverse_ratio.uID2 = ratio.uID1;
+ reverse_ratio.ratio = 1.0 / ratio.ratio;
+ database->saveUnitRatio( &reverse_ratio );
+ conversionTable->setRatio( reverse_ratio );
+
+#if 0
+ UnitRatioList ratioList;
+ database->loadUnitRatios( &ratioList, Unit::Mass );
+
+ saveAllRatios( ratioList );
+#endif
+}
+
+void UnitsDialog::removeRatio( int r, int c )
+{
+ ConversionTable *conversionTable = (ConversionTable*)sender();
+ database->removeUnitRatio( conversionTable->getUnitID( r ), conversionTable->getUnitID( c ) );
+}
+
+void UnitsDialog::saveAllRatios( UnitRatioList &ratioList )
+{
+#if 0
+ ConversionTable *conversionTable = massConversionTable;
+
+ KProgressDialog progress_dialog( this, "progress_dialog", i18n( "Finding Unit Ratios" ), QString::null, true );
+ progress_dialog.progressBar() ->setTotalSteps( ratioList.count() * ratioList.count() );
+
+ for ( UnitRatioList::const_iterator current_it = ratioList.begin(); current_it != ratioList.end(); ++current_it ) {
+ UnitRatio current_ratio = *current_it;
+ for ( UnitRatioList::const_iterator ratio_it = ratioList.begin(); ratio_it != ratioList.end(); ++ratio_it ) {
+ if ( progress_dialog.wasCancelled() )
+ return ;
+
+ progress_dialog.progressBar() ->advance( 1 );
+ kapp->processEvents();
+
+ UnitRatio new_ratio;
+ new_ratio.uID1 = current_ratio.uID1;
+ new_ratio.uID2 = ( *ratio_it ).uID2;
+ new_ratio.ratio = ( *ratio_it ).ratio * current_ratio.ratio;
+
+ if ( ratioList.contains( new_ratio ) )
+ continue;
+
+ if ( ( ( *ratio_it ).uID1 == current_ratio.uID2 ) && ( ( *ratio_it ).uID2 != current_ratio.uID1 ) ) {
+ UnitRatio reverse_ratio;
+ reverse_ratio.uID1 = new_ratio.uID2;
+ reverse_ratio.uID2 = new_ratio.uID1;
+ reverse_ratio.ratio = 1.0 / new_ratio.ratio;
+
+ database->saveUnitRatio( &new_ratio );
+ database->saveUnitRatio( &reverse_ratio );
+ conversionTable->setRatio( new_ratio );
+ conversionTable->setRatio( reverse_ratio );
+ //ratioList.append(new_ratio); ratioList.append(reverse_ratio);
+ }
+ }
+ }
+#endif
+}
+
+#include "unitsdialog.moc"
diff --git a/krecipes/src/dialogs/unitsdialog.h b/krecipes/src/dialogs/unitsdialog.h
new file mode 100644
index 0000000..d11b9f0
--- /dev/null
+++ b/krecipes/src/dialogs/unitsdialog.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef UNITSDIALOG_H
+#define UNITSDIALOG_H
+#include <qwidget.h>
+#include <klistview.h>
+#include <qpushbutton.h>
+
+#include "datablocks/unitratiolist.h"
+#include "datablocks/unit.h"
+#include "widgets/dblistviewbase.h"
+
+class RecipeDB;
+class ConversionTable;
+class StdUnitListView;
+
+/**
+@author Unai Garro
+*/
+class UnitsDialog: public QWidget
+{
+ Q_OBJECT
+public:
+ UnitsDialog( QWidget *parent, RecipeDB *db );
+ ~UnitsDialog();
+ virtual QSize sizeHint () const
+ {
+ return QSize( 300, 200 );
+ }
+
+public slots:
+ void reload( ReloadFlags flag = Load );
+
+private:
+ // Widgets
+ StdUnitListView *unitListView;
+ ConversionTable *massConversionTable;
+ ConversionTable *volumeConversionTable;
+ QPushButton *newUnitButton;
+ QPushButton *removeUnitButton;
+
+ // Internal methods
+ void saveAllRatios( UnitRatioList &ratioList );
+ bool checkBounds( const QString &name );
+
+ // Internal Variables
+ RecipeDB *database;
+private slots:
+ void loadConversionTables();
+ void loadConversionTable( ConversionTable*, Unit::Type );
+ void saveRatio( int r, int c, double value );
+ void removeRatio( int r, int c );
+};
+
+#endif
diff --git a/krecipes/src/dialogs/usdadatadialog.cpp b/krecipes/src/dialogs/usdadatadialog.cpp
new file mode 100644
index 0000000..25f74d3
--- /dev/null
+++ b/krecipes/src/dialogs/usdadatadialog.cpp
@@ -0,0 +1,200 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "usdadatadialog.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <klineedit.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <qfile.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qtextstream.h>
+#include <qvbox.h>
+
+#include "backends/recipedb.h"
+#include "backends/usda_property_data.h"
+#include "backends/usda_unit_data.h"
+#include "widgets/krelistview.h"
+#include "datablocks/weight.h"
+
+USDADataDialog::USDADataDialog( const Element &ing, RecipeDB *db, QWidget *parent )
+ : KDialogBase( parent, "usdaDataDialog", true, QString::null,
+ KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok ),
+ ingredient( ing ),
+ database( db )
+{
+ setCaption( QString( i18n( "Load ingredient properties for: \"%1\"" ) ).arg( ingredient.name ) );
+
+ QVBox *page = makeVBoxMainWidget();
+
+ setButtonText( KDialogBase::Ok, i18n( "&Load" ) );
+
+ KreListView *krelistview = new KreListView( page, QString::null, true, 0 );
+
+ listView = krelistview->listView();
+ listView->addColumn( i18n( "USDA Ingredient" ) );
+ listView->addColumn( i18n( "Id" ) );
+ listView->setAllColumnsShowFocus( true );
+
+ loadDataFromFile();
+
+ connect( listView, SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), this, SLOT( slotOk() ) );
+}
+
+USDADataDialog::~USDADataDialog()
+{}
+
+void USDADataDialog::loadDataFromFile()
+{
+ QString abbrev_file = locate( "appdata", "data/abbrev.txt" );
+ if ( abbrev_file.isEmpty() ) {
+ kdDebug() << "Unable to find abbrev.txt data file." << endl;
+ return ;
+ }
+
+ QFile file( abbrev_file );
+ if ( !file.open( IO_ReadOnly ) ) {
+ kdDebug() << "Unable to open data file: " << abbrev_file << endl;
+ return ;
+ }
+
+ int index = 0;
+ QTextStream stream( &file );
+ while ( !stream.atEnd() ) {
+ QString line = stream.readLine();
+ if ( line.isEmpty() ) {
+ continue;
+ }
+
+ QStringList fields = QStringList::split( "^", line, true );
+ loaded_data << fields;
+
+ QString ing_id = fields[ 0 ].mid( 1, fields[ 1 ].length() - 2 );
+ QString ing_name = fields[ 1 ].mid( 1, fields[ 1 ].length() - 2 );
+ ( void ) new QListViewItem( listView, ing_name, QString::number( index ) ); //using an index instead of the actual id will help find the data later
+
+ index++;
+ }
+}
+
+void USDADataDialog::slotOk()
+{
+ QListViewItem * item = listView->selectedItem();
+ if ( item ) {
+ int index = item->text( 1 ).toInt();
+ QStringList data = loaded_data[ index ];
+
+ int grams_id = database->findExistingUnitByName( "g" ); //get this id because all data is given per gram
+ if ( grams_id == -1 ) {
+ //FIXME: take advantage of abbreviations
+ Unit unit("g","g");
+ unit.type = Unit::Mass;
+ database->createNewUnit( unit );
+ grams_id = database->lastInsertID();
+ }
+ else {
+ Unit unit = database->unitName(grams_id);
+ if ( unit.type != Unit::Mass ) {
+ unit.type = Unit::Mass;
+ database->modUnit( unit );
+ }
+ }
+
+ IngredientPropertyList property_list;
+ database->loadProperties( &property_list );
+ IngredientPropertyList existing_ing_props;
+ database->loadProperties( &existing_ing_props, ingredient.id );
+
+ int i = 0;
+ for ( QStringList::const_iterator it = data.at( 2 ); !property_data_list[ i ].name.isEmpty(); ++it, ++i ) {
+ int property_id = property_list.findByName( property_data_list[ i ].name );
+ if ( property_id == -1 ) {
+ database->addProperty( property_data_list[ i ].name, property_data_list[ i ].unit );
+ property_id = database->lastInsertID();
+ }
+
+ double amount = ( *it ).toDouble() / 100.0; //data givin per 100g so divide by 100 to get the amount in 1 gram
+
+ if ( existing_ing_props.find( property_id ) != existing_ing_props.end() ) //property already added to ingredient, so just update
+ database->changePropertyAmountToIngredient( ingredient.id, property_id, amount, grams_id );
+ else
+ database->addPropertyToIngredient( ingredient.id, property_id, amount, grams_id );
+ }
+
+ i+=2;
+
+ int i_initial = i;
+ WeightList weights = database->ingredientWeightUnits( ingredient.id );
+ for ( ; i < i_initial+3; ++i ) {
+ Weight w;
+ w.weight = data[i].toDouble();
+
+ i++;
+
+ QString amountAndWeight = data[i].mid( 1, data[i].length() - 2 );
+ if ( !amountAndWeight.isEmpty() ) {
+ int spaceIndex = amountAndWeight.find(" ");
+ w.perAmount = amountAndWeight.left(spaceIndex).toDouble();
+
+ QString perAmountUnit = amountAndWeight.right(amountAndWeight.length()-spaceIndex-1);
+ if ( !parseUSDAUnitAndPrep( perAmountUnit, w.perAmountUnit, w.prepMethod ) )
+ continue;
+
+ int unitID = database->findExistingUnitByName( w.perAmountUnit );
+ if ( unitID == -1 ) {
+ for ( int i = 0; unit_data_list[ i ].name; ++i ) {
+ if ( w.perAmountUnit == unit_data_list[ i ].name || w.perAmountUnit == unit_data_list[ i ].plural ) {
+ database->createNewUnit( Unit(unit_data_list[ i ].name,unit_data_list[ i ].plural) );
+ }
+ }
+
+ unitID = database->lastInsertID();
+ }
+ w.perAmountUnitID = unitID;
+
+ if ( !w.prepMethod.isEmpty() ) {
+ int prepID = database->findExistingPrepByName( w.prepMethod );
+ if ( prepID == -1 ) {
+ database->createNewPrepMethod( w.prepMethod );
+ prepID = database->lastInsertID();
+ }
+ w.prepMethodID = prepID;
+ }
+
+ bool exists = false;
+ for ( WeightList::const_iterator it = weights.begin(); it != weights.end(); ++it ) {
+ if ( (*it).perAmountUnitID == w.perAmountUnitID && (*it).prepMethodID == w.prepMethodID ) {
+ exists = true;
+ break;
+ }
+ }
+ if ( exists )
+ continue;
+
+ w.weightUnitID = grams_id;
+ w.ingredientID = ingredient.id;
+ database->addIngredientWeight( w );
+ }
+ }
+
+ accept();
+ }
+ else
+ reject();
+}
+
+
+#include "usdadatadialog.moc"
diff --git a/krecipes/src/dialogs/usdadatadialog.h b/krecipes/src/dialogs/usdadatadialog.h
new file mode 100644
index 0000000..6c0712c
--- /dev/null
+++ b/krecipes/src/dialogs/usdadatadialog.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef USDADATADIALOG_H
+#define USDADATADIALOG_H
+
+#include <kdialogbase.h>
+
+#include <qstringlist.h>
+#include <qvaluelist.h>
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+
+class KListView;
+
+class USDADataDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ USDADataDialog( const Element &, RecipeDB *database, QWidget *parent = 0 );
+ ~USDADataDialog();
+
+private:
+ void loadDataFromFile();
+
+ KListView *listView;
+
+ Element ingredient;
+ RecipeDB *database;
+
+ QValueList<QStringList> loaded_data;
+
+private slots:
+ void slotOk();
+};
+
+#endif //USDADATADIALOG_H
diff --git a/krecipes/src/exporters/Makefile.am b/krecipes/src/exporters/Makefile.am
new file mode 100644
index 0000000..fcedc46
--- /dev/null
+++ b/krecipes/src/exporters/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. $(all_includes)
+
+METASOURCES = AUTO
+noinst_LTLIBRARIES = libkrecipesexporters.la
+
+libkrecipesexporters_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+libkrecipesexporters_la_SOURCES = kreexporter.cpp baseexporter.cpp cookmlexporter.cpp \
+ recipemlexporter.cpp mmfexporter.cpp htmlexporter.cpp plaintextexporter.cpp \
+ rezkonvexporter.cpp htmlbookexporter.cpp
+
+noinst_HEADERS = kreexporter.h baseexporter.h cookmlexporter.h \
+ recipemlexporter.h mmfexporter.h htmlexporter.h plaintextexporter.h
diff --git a/krecipes/src/exporters/baseexporter.cpp b/krecipes/src/exporters/baseexporter.cpp
new file mode 100644
index 0000000..3fe10b1
--- /dev/null
+++ b/krecipes/src/exporters/baseexporter.cpp
@@ -0,0 +1,172 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "baseexporter.h"
+
+#include <qfile.h>
+#include <qfileinfo.h>
+
+#include <kaboutdata.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kmessagebox.h>
+#include <ktar.h>
+#include <kstandarddirs.h>
+
+#include "backends/recipedb.h"
+
+BaseExporter::BaseExporter( const QString& _filename, const QString &format ) :
+ file( 0 ),
+ tar_file( 0 ),
+ filename( _filename ),
+ m_progress_dlg( 0 ),
+ compress(false)
+{
+ //automatically append extension
+ QString extension = format.right( format.length()-2 );
+ if ( filename.right( extension.length() ) != extension )
+ filename += "." + extension;
+}
+
+BaseExporter::~BaseExporter()
+{
+ delete file;
+ delete tar_file;
+}
+
+int BaseExporter::headerFlags() const
+{
+ return RecipeDB::None;
+}
+
+void BaseExporter::setCompressed( bool b )
+{
+ compress = b;
+}
+
+void BaseExporter::exporter( const QValueList<int> &ids, RecipeDB *database, KProgressDialog *progress_dlg )
+{
+ m_progress_dlg = progress_dlg;
+
+ if ( createFile() )
+ saveToFile( ids, database );
+ else
+ kdDebug() << "no output file has been selected for export." << endl;
+}
+
+void BaseExporter::exporter( int id, RecipeDB *database, KProgressDialog *progress_dlg )
+{
+ QValueList<int> single_recipe_list;
+ single_recipe_list << id ;
+ exporter( single_recipe_list, database, progress_dlg );
+}
+
+void BaseExporter::writeStream( QTextStream &stream, const RecipeList &recipe_list )
+{
+ stream << createHeader(recipe_list);
+ stream << createContent(recipe_list);
+ stream << createFooter();
+}
+
+bool BaseExporter::createFile()
+{
+ if ( file )
+ return true;
+
+ if ( !filename.isEmpty() ) {
+ if ( compress ) {
+ tar_file = new KTar( filename, "application/x-gzip" );
+ QFileInfo fi( filename );
+ file = new QFile( locateLocal( "tmp",fi.fileName()+"ml" ) );
+ }
+ else
+ file = new QFile(filename);
+
+ return (file != 0);
+ }
+ else
+ return false;
+}
+
+QString BaseExporter::fileName() const
+{
+ return filename;
+}
+
+void BaseExporter::saveToFile( const QValueList<int> &ids, RecipeDB *database )
+{
+ if ( file->open( IO_WriteOnly ) ) {
+ if ( m_progress_dlg )
+ m_progress_dlg->progressBar()->setTotalSteps( ids.count()/progressInterval() );
+
+ QValueList<int> ids_copy = ids;
+ QTextStream stream( file );
+ stream.setEncoding( QTextStream::UnicodeUTF8 );
+
+ RecipeList recipe_list;
+ if ( headerFlags() != RecipeDB::None ) {
+ database->loadRecipes( &recipe_list, headerFlags(), ids );
+ }
+ stream << createHeader( recipe_list );
+
+ recipe_list.clear();
+ for ( uint i = 0; i < ids.count(); i += progressInterval() ) {
+ QValueList<int> sub_list;
+ for ( int sub_i = 0; sub_i < progressInterval(); ++sub_i ) {
+ if ( ids_copy.count() == 0 ) break;
+
+ sub_list << *ids_copy.begin();
+ ids_copy.remove( ids_copy.begin() );
+ }
+
+ RecipeList recipe_list;
+ database->loadRecipes( &recipe_list, supportedItems(), sub_list );
+
+ QString content = createContent( recipe_list );
+ if ( !content.isEmpty() )
+ stream << content;
+
+ if ( m_progress_dlg && m_progress_dlg->wasCancelled() )
+ break;
+
+ if ( m_progress_dlg ) {
+ m_progress_dlg->progressBar()->advance( progressInterval() );
+ kapp->processEvents();
+ }
+ }
+
+ stream << createFooter();
+
+ if ( tar_file && tar_file->open( IO_WriteOnly ) ) {
+ //close, which flushes the buffer, and then open for reading
+ file->close();
+ file->open( IO_ReadOnly );
+
+ QFileInfo fi( file->name() );
+ QByteArray data = file->readAll();
+ tar_file->writeFile( fi.fileName(), fi.owner(), fi.group(), data.size(), data );
+ tar_file->close();
+ }
+
+ file->close();
+ }
+}
+
+QString BaseExporter::krecipes_version() const
+{
+ KInstance * this_instance = KGlobal::instance();
+ if ( this_instance && this_instance->aboutData() )
+ return this_instance->aboutData() ->version();
+
+ return QString::null; //Oh, well. We couldn't get the version.
+}
+
diff --git a/krecipes/src/exporters/baseexporter.h b/krecipes/src/exporters/baseexporter.h
new file mode 100644
index 0000000..bf909fe
--- /dev/null
+++ b/krecipes/src/exporters/baseexporter.h
@@ -0,0 +1,95 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef BASEEXPORTER_H
+#define BASEEXPORTER_H
+
+#include <qstringlist.h>
+
+#include <kapplication.h>
+#include <kprogress.h>
+
+#include "datablocks/recipelist.h"
+
+class QFile;
+
+class KTar;
+
+class RecipeDB;
+
+class BaseExporter
+{
+public:
+ BaseExporter( const QString &file, const QString &ext );
+ virtual ~BaseExporter();
+
+ /** Subclasses must report which items it is able to work with.
+ * These should be or'ed together items from RecipeDB::RecipeItems
+ */
+ virtual int supportedItems() const = 0;
+
+ /** Export the recipes with the given ids to the file specified in the constructor.
+ * Optionally, a progress dialog may be given to specify the progress made.
+ */
+ void exporter( const QValueList<int> &ids, RecipeDB *database, KProgressDialog * = 0 );
+
+ /** Convenience function for the above, which exports a single recipe. */
+ void exporter( int id, RecipeDB *database, KProgressDialog * = 0 );
+
+ /** Returns the actual filename that will be written to during the export.
+ * Note that this can differ somewhat from the filename passed in the
+ * constructor.
+ */
+ QString fileName() const;
+
+ /** Write the given recipe list to a text stream.
+ * This can be used to export recipes without use of the database.
+ */
+ void writeStream( QTextStream &, const RecipeList & );
+
+protected:
+ virtual QString createContent( const RecipeList & ) = 0;
+ virtual QString createFooter(){ return QString(); }
+ virtual QString createHeader( const RecipeList & ){ return QString(); }
+
+ /** The number of recipes to load into memory at once. This many recipes will be
+ * loaded from the database, processed, and then another batch of this many will be
+ * processed until all recipes are exported.
+ */
+ virtual int progressInterval() const { return 50; }
+
+ /** Extra RecipeDB::RecipeItems that a subclass requires when creating a file's header.
+ * For example, the Krecipes file format requires writing the category hierarchy in the header,
+ * so it's exporter adds RecipeDB::Categories.
+ */
+ virtual int headerFlags() const;
+
+ /** Make generated file a gzipped tarball */
+ void setCompressed( bool );
+
+ /** Attempt to return the version of the application via
+ * KGlobal::instance()->aboutData()->version()
+ * This can be used by exporters to put the version of the app exporting the file.
+ */
+ QString krecipes_version() const;
+
+private:
+ bool createFile();
+ void saveToFile( const QValueList<int> &ids, RecipeDB *database );
+
+ QFile* file;
+ KTar *tar_file;
+ QString filename;
+ KProgressDialog *m_progress_dlg;
+ bool compress;
+};
+
+#endif //BASEEXPORTER_H
diff --git a/krecipes/src/exporters/cookmlexporter.cpp b/krecipes/src/exporters/cookmlexporter.cpp
new file mode 100644
index 0000000..7b5988e
--- /dev/null
+++ b/krecipes/src/exporters/cookmlexporter.cpp
@@ -0,0 +1,122 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "cookmlexporter.h"
+
+#include <qbuffer.h>
+#include <qdom.h>
+#include <qimage.h>
+#include <qpixmap.h>
+#include <qfile.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <ktempfile.h>
+#include <kmdcodec.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+#include "backends/recipedb.h"
+
+CookMLExporter::CookMLExporter( const QString& filename, const QString &format ) :
+ BaseExporter( filename, format )
+{}
+
+
+CookMLExporter::~CookMLExporter()
+{}
+
+int CookMLExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Ratings;
+}
+
+QString CookMLExporter::createHeader( const RecipeList& )
+{
+ QString xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ xml += "<!DOCTYPE cookml PUBLIC \"-\" \"cookml.dtd\">";
+ xml += "<cookml version=\"1.0.13\" prog=\"Krecipes\" progver=\""+krecipes_version()+"\">";
+ return xml;
+}
+
+QString CookMLExporter::createFooter()
+{
+ return "</cookml>";
+}
+
+QString CookMLExporter::createContent( const RecipeList& recipes )
+{
+ QString xml;
+ QDomDocument doc;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ QDomElement recipe_tag = doc.createElement( "recipe" );
+ recipe_tag.setAttribute( "lang", ( KGlobal::locale() ) ->language() );
+
+ //cookml_tag.appendChild( recipe_tag );
+
+ QDomElement head_tag = doc.createElement( "head" );
+ head_tag.setAttribute( "title", ( *recipe_it ).title );
+ head_tag.setAttribute( "servingqty", ( *recipe_it ).yield.amount );
+ head_tag.setAttribute( "servingtype", ( *recipe_it ).yield.type );
+ head_tag.setAttribute( "rid", i18n( "" ) ); //FIXME:what's this...recipe ID??
+ recipe_tag.appendChild( head_tag );
+
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ QDomElement cat_tag = doc.createElement( "cat" );
+ cat_tag.appendChild( doc.createTextNode( ( *cat_it ).name ) );
+ head_tag.appendChild( cat_tag );
+ }
+
+ for ( ElementList::const_iterator author_it = ( *recipe_it ).authorList.begin(); author_it != ( *recipe_it ).authorList.end(); ++author_it ) {
+ QDomElement sourceline_tag = doc.createElement( "sourceline" );
+ sourceline_tag.appendChild( doc.createTextNode( ( *author_it ).name ) );
+ head_tag.appendChild( sourceline_tag );
+ }
+
+ QDomElement picbin_tag = doc.createElement( "picbin" );
+ picbin_tag.setAttribute( "format", "JPG" );
+
+ QByteArray data;
+ QBuffer buffer( data );
+ buffer.open( IO_WriteOnly );
+ QImageIO iio( &buffer, "JPEG" );
+ iio.setImage( ( *recipe_it ).photo.convertToImage() );
+ iio.write();
+ //( *recipe_it ).photo.save( &buffer, "JPEG" ); don't need QImageIO in QT 3.2
+
+ picbin_tag.appendChild( doc.createTextNode( KCodecs::base64Encode( data, true ) ) );
+ head_tag.appendChild( picbin_tag );
+
+ QDomElement part_tag = doc.createElement( "part" );
+ for ( IngredientList::const_iterator ing_it = ( *recipe_it ).ingList.begin(); ing_it != ( *recipe_it ).ingList.end(); ++ing_it ) {
+ QDomElement ingredient_tag = doc.createElement( "ingredient" );
+ ingredient_tag.setAttribute( "qty", QString::number( ( *ing_it ).amount ) );
+ ingredient_tag.setAttribute( "unit", ( ( *ing_it ).amount > 1 ) ? ( *ing_it ).units.plural : ( *ing_it ).units.name );
+ ingredient_tag.setAttribute( "item", ( *ing_it ).name );
+ ingredient_tag.setAttribute( "preparation", ( *ing_it ).prepMethodList.join(",") );
+ part_tag.appendChild( ingredient_tag );
+ }
+ recipe_tag.appendChild( part_tag );
+
+ QDomElement preparation_tag = doc.createElement( "preparation" );
+ recipe_tag.appendChild( preparation_tag );
+
+ QDomElement text_tag = doc.createElement( "text" );
+ preparation_tag.appendChild( text_tag );
+ text_tag.appendChild( doc.createTextNode( ( *recipe_it ).instructions ) );
+
+ xml += recipe_tag.text();
+ }
+
+ return xml;
+}
diff --git a/krecipes/src/exporters/cookmlexporter.h b/krecipes/src/exporters/cookmlexporter.h
new file mode 100644
index 0000000..ce62b73
--- /dev/null
+++ b/krecipes/src/exporters/cookmlexporter.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef COOKMLEXPORTER_H
+#define COOKMLEXPORTER_H
+
+#include "baseexporter.h"
+
+/**
+ * Export class for the Meal-Master file format
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * ---none?---
+ */
+class CookMLExporter : public BaseExporter
+{
+public:
+ CookMLExporter( const QString&, const QString& );
+ virtual ~CookMLExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual QString createContent( const RecipeList& );
+ virtual QString createHeader( const RecipeList& );
+ virtual QString createFooter();
+};
+
+#endif //COOKMLEXPORTER_H
diff --git a/krecipes/src/exporters/htmlbookexporter.cpp b/krecipes/src/exporters/htmlbookexporter.cpp
new file mode 100644
index 0000000..e1e98f4
--- /dev/null
+++ b/krecipes/src/exporters/htmlbookexporter.cpp
@@ -0,0 +1,160 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "htmlbookexporter.h"
+
+#include <qfile.h>
+#include <qstylesheet.h>
+
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/categorytree.h"
+
+HTMLBookExporter::HTMLBookExporter( CategoryTree *categories, const QString& basedir, const QString &format ) :
+ HTMLExporter( basedir+"/index", format ), m_categories(categories), m_basedir(basedir)
+{
+}
+
+HTMLBookExporter::~HTMLBookExporter()
+{
+}
+
+int HTMLBookExporter::headerFlags() const
+{
+ return RecipeDB::Categories | RecipeDB::Title;
+}
+
+QString HTMLBookExporter::createContent( const RecipeList& recipes )
+{
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ QMap<QString,QTextStream*>::iterator stream_it = fileMap.find( (*cat_it).name );
+ (**stream_it) << "<br /><br />";
+ (**stream_it) << QString("<a name=\""+(*recipe_it).title+"\" />");
+ (**stream_it) << HTMLExporter::createContent(*recipe_it);
+ (**stream_it) << QString("[ <a href=\"#top\">Top</a> ]");
+ (**stream_it) << QString("[ <a href=\"index.html\">Back</a> ]");
+ (**stream_it) << "<br /><br />";
+ }
+ }
+
+ return QString::null;
+}
+
+QString HTMLBookExporter::createHeader( const RecipeList &list )
+{
+ QString output = HTMLExporter::createHeader(list);
+
+ QString catLinks;
+ QTextStream catLinksStream(&catLinks,IO_WriteOnly);
+ createCategoryStructure(catLinksStream,list);
+
+ return output+"<h1>Krecipes Recipes</h1><div>"+catLinks+"</li></ul></div>";
+}
+
+QString HTMLBookExporter::createFooter()
+{
+ QMap<QString,QTextStream*>::const_iterator it;
+ for ( it = fileMap.begin(); it != fileMap.end(); ++it ) {
+ (**it) << HTMLExporter::createFooter();
+
+ (*it)->device()->close();
+
+ //does it matter the order of deletion here?
+ QIODevice *file = (*it)->device();
+ delete *it;
+ delete file;
+ }
+
+ QString output = HTMLExporter::createFooter();
+ return output;
+}
+
+void HTMLBookExporter::createCategoryStructure( QTextStream &xml, const RecipeList &recipes )
+{
+ QValueList<int> categoriesUsed;
+ for ( RecipeList::const_iterator recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ QMap<QString,QTextStream*>::iterator stream_it = fileMap.find( (*cat_it).name );
+ if ( categoriesUsed.find( ( *cat_it ).id ) == categoriesUsed.end() ) {
+ categoriesUsed << ( *cat_it ).id;
+
+ QString catPageName = m_basedir+"/"+escape((*cat_it).name)+".html";
+ QFile *catPage = new QFile( catPageName );
+ catPage->open( IO_WriteOnly );
+ QTextStream *stream = new QTextStream( catPage );
+ stream->setEncoding( QTextStream::UnicodeUTF8 );
+ (*stream) << HTMLExporter::createHeader(recipes);
+ (*stream) << QString("<a name=\"top\" />");
+ (*stream) << "<h1>"<<(*cat_it).name<<"</h1>";
+
+ stream_it = fileMap.insert((*cat_it).name,stream);
+ }
+ (**stream_it) << QString("[ <a href=\"#" + (*recipe_it).title + "\">" + (*recipe_it).title + "</a> ]");
+ }
+ }
+
+ if ( !categoriesUsed.empty() ) {
+ //only keep the relevant category structure
+ removeIfUnused( categoriesUsed, m_categories );
+
+ xml << "<ul>\n";
+ writeCategoryStructure( xml, m_categories );
+ xml << "</ul>\n";
+ }
+}
+
+bool HTMLBookExporter::removeIfUnused( const QValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show )
+{
+ for ( CategoryTree * it = parent->firstChild(); it; it = it->nextSibling() ) {
+ if ( cat_ids.find( it->category.id ) != cat_ids.end() ) {
+ parent_should_show = true;
+ removeIfUnused( cat_ids, it, true ); //still recurse, but doesn't affect 'parent'
+ }
+ else {
+ bool result = removeIfUnused( cat_ids, it );
+ if ( parent_should_show == false )
+ parent_should_show = result;
+ }
+ }
+
+ if ( !parent_should_show && parent->category.id != -1 ) {
+ //FIXME: CategoryTree is broken when deleting items
+ //delete parent;
+
+ parent->category.id = -2; //temporary workaround
+ }
+
+ return parent_should_show;
+}
+
+void HTMLBookExporter::writeCategoryStructure( QTextStream &xml, const CategoryTree *categoryTree )
+{
+ if ( categoryTree->category.id != -2 ) {
+ if ( categoryTree->category.id != -1 ) {
+ QString catPageName = QStyleSheet::escape(categoryTree->category.name)+".html";
+
+ xml << "\t<li>\n\t\t<a href=\""+catPageName+"\">"+QStyleSheet::escape( categoryTree->category.name ).replace("\"","&quot;") + "</a>\n";
+ }
+
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ if ( categoryTree->parent() != 0 )
+ xml << "<ul>\n";
+ writeCategoryStructure( xml, child_it );
+ if ( categoryTree->parent() != 0 )
+ xml << "</ul>\n";
+ }
+
+ if ( categoryTree->category.id != -1 )
+ xml << "\t</li>\n";
+ }
+}
diff --git a/krecipes/src/exporters/htmlbookexporter.h b/krecipes/src/exporters/htmlbookexporter.h
new file mode 100644
index 0000000..ca0d7fe
--- /dev/null
+++ b/krecipes/src/exporters/htmlbookexporter.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef HTMLBOOKEXPORTER_H
+#define HTMLBOOKEXPORTER_H
+
+#include <qmap.h>
+#include <qvaluelist.h>
+
+#include "baseexporter.h"
+#include "htmlexporter.h"
+
+class RecipeDB;
+class CategoryTree;
+
+/**
+ * Exports a given recipe list as HTML
+ * @author Jason Kivlighn
+ */
+class HTMLBookExporter : public HTMLExporter
+{
+public:
+ HTMLBookExporter( CategoryTree *categories, const QString&, const QString& );
+ virtual ~HTMLBookExporter();
+
+protected:
+ virtual QString createContent( const RecipeList & );
+ virtual QString createHeader( const RecipeList & );
+ virtual QString createFooter();
+
+ virtual int headerFlags() const;
+
+private:
+ void createCategoryStructure( QTextStream &xml, const RecipeList &recipes );
+ bool removeIfUnused( const QValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show = false );
+ void writeCategoryStructure( QTextStream &xml, const CategoryTree *categoryTree );
+
+ QMap<QString,QTextStream*> fileMap;
+
+ RecipeDB *database;
+ CategoryTree *m_categories;
+ QString m_basedir;
+};
+
+#endif //HTMLBOOKEXPORTER_H
diff --git a/krecipes/src/exporters/htmlexporter.cpp b/krecipes/src/exporters/htmlexporter.cpp
new file mode 100644
index 0000000..0d56c98
--- /dev/null
+++ b/krecipes/src/exporters/htmlexporter.cpp
@@ -0,0 +1,570 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "htmlexporter.h"
+
+#include <qptrdict.h>
+#include <qimage.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qstylesheet.h> //for QStyleSheet::escape() to escape for HTML
+#include <dom/dom_element.h>
+#include <qpainter.h>
+#include <qfileinfo.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <khtml_part.h>
+#include <khtmlview.h>
+#include <kprogress.h>
+#include <kstandarddirs.h>
+#include <kurl.h>
+#include <kiconloader.h>
+
+#include "datablocks/mixednumber.h"
+#include "backends/recipedb.h"
+#include "dialogs/setupdisplay.h"
+#include "image.h"
+#include "krepagelayout.h"
+
+#include <cmath> //for ceil()
+
+HTMLExporter::HTMLExporter( const QString& filename, const QString &format ) :
+ BaseExporter( filename, format )
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Page Setup" );
+
+ //let's do everything we can to be sure at least some layout is loaded
+ QString template_filename = config->readEntry( "Template", locate( "appdata", "layouts/Default.template" ) );
+ if ( template_filename.isEmpty() || !QFile::exists( template_filename ) )
+ template_filename = locate( "appdata", "layouts/Default.template" );
+ kdDebug() << "Using template file: " << template_filename << endl;
+
+ setTemplate( template_filename );
+
+ //let's do everything we can to be sure at least some layout is loaded
+ m_layoutFilename = config->readEntry( "Layout", locate( "appdata", "layouts/Default.klo" ) );
+ if ( m_layoutFilename.isEmpty() || !QFile::exists( m_layoutFilename ) )
+ m_layoutFilename = locate( "appdata", "layouts/Default.klo" );
+ kdDebug() << "Using layout file: " << m_layoutFilename << endl;
+}
+
+HTMLExporter::~HTMLExporter()
+{
+}
+
+void HTMLExporter::setTemplate( const QString &filename )
+{
+ QFile templateFile( filename );
+ if ( templateFile.open( IO_ReadOnly ) ) {
+ m_templateFilename = filename;
+ m_templateContent = QString( templateFile.readAll() );
+ }
+ else
+ kdDebug()<<"couldn't find/open template file"<<endl;
+}
+
+void HTMLExporter::setStyle( const QString &filename )
+{
+ m_layoutFilename = filename;
+}
+
+int HTMLExporter::supportedItems() const
+{
+ int items = RecipeDB::All ^ RecipeDB::Properties;
+
+ QMap<QString,bool>::const_iterator it = m_visibilityMap.find("properties");
+ if ( it == m_visibilityMap.end() || it.data() == true )
+ items |= RecipeDB::Properties;
+
+ return RecipeDB::All;
+}
+
+QString HTMLExporter::createContent( const Recipe& recipe )
+{
+ QString templateCopy = m_templateContent;
+
+ storePhoto( recipe );
+
+ populateTemplate( recipe, templateCopy );
+ return templateCopy;
+}
+
+QString HTMLExporter::createContent( const RecipeList& recipes )
+{
+ QString fileContent;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ fileContent += createContent(*recipe_it);
+ }
+
+ return fileContent;
+}
+
+QString HTMLExporter::createHeader( const RecipeList & )
+{
+ m_visibilityMap.clear();
+ m_columnsMap.clear();
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Page Setup" );
+
+ m_error = false;
+
+ if ( m_templateContent.isEmpty() ) {
+ QString errorStr = i18n("<html><body>\n"
+ "<p><b>Error: </b>Unable to find a layout file, which is"
+ " needed to view the recipe.</p>"
+ "<p>Krecipes was probably not properly installed.</p>"
+ "</body></html>");
+ m_error = true;
+ return errorStr;
+ }
+
+ QFile layoutFile( m_layoutFilename );
+ QString error; int line; int column;
+ QDomDocument doc;
+ if ( !doc.setContent( &layoutFile, &error, &line, &column ) ) {
+ kdDebug()<<"Unable to load style information. Will create HTML without it..."<<endl;
+ }
+ else
+ processDocument(doc);
+
+ //put all the recipe photos into this directory
+ QDir dir;
+ QFileInfo fi(fileName());
+ dir.mkdir( fi.dirPath(true) + "/" + fi.baseName() + "_photos" );
+
+ RecipeList::const_iterator recipe_it;
+
+ KLocale*loc = KGlobal::locale();
+ QString encoding = loc->encoding();
+
+ QString output = "<html>";
+ output += "<head>";
+ output += "<meta name=\"lang\" content=\"" + loc->language() + "\">\n";
+ output += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
+ output += QString( "<title>%1</title>" ).arg( i18n( "Krecipes Recipes" ) );
+
+ output += "<style type=\"text/css\">\n";
+
+ QString cssContent;
+ QFileInfo info(m_templateFilename);
+ QFile cssFile(info.dirPath(true) + "/" + info.baseName() + ".css");
+ kdDebug()<<info.dirPath(true) + "/" + info.baseName() + ".css"<<endl;
+ if ( cssFile.open( IO_ReadOnly ) ) {
+ cssContent = QString( cssFile.readAll() );
+ }
+ output += cssContent;
+
+ output += m_cachedCSS;
+ m_cachedCSS = QString::null;
+ output += "</style>";
+ output += "</head>";
+ output += "<body class=\"background\">";
+
+ return output;
+}
+
+void HTMLExporter::beginObject( const QString &object )
+{
+ m_cachedCSS += "."+object+" { \n";
+}
+
+void HTMLExporter::endObject()
+{
+ m_cachedCSS += " } \n";
+}
+
+void HTMLExporter::loadBackgroundColor( const QString &/*object*/, const QColor& color )
+{
+ m_cachedCSS += bgColorAsCSS(color);
+}
+
+void HTMLExporter::loadFont( const QString &/*object*/, const QFont& font )
+{
+ m_cachedCSS += fontAsCSS(font);
+}
+
+void HTMLExporter::loadTextColor( const QString &/*object*/, const QColor& color )
+{
+ m_cachedCSS += textColorAsCSS(color);
+}
+
+void HTMLExporter::loadVisibility( const QString &object, bool visible )
+{
+ m_cachedCSS += visibilityAsCSS(visible);
+ m_visibilityMap.insert(object,visible);
+}
+
+void HTMLExporter::loadAlignment( const QString &/*object*/, int alignment )
+{
+ m_cachedCSS += alignmentAsCSS(alignment);
+}
+
+void HTMLExporter::loadBorder( const QString &/*object*/, const KreBorder& border )
+{
+ m_cachedCSS += borderAsCSS(border);
+}
+
+void HTMLExporter::loadColumns( const QString & object, int cols )
+{
+ m_columnsMap.insert(object,cols);
+kdDebug()<<object<<" has "<<cols<<" columns"<<endl;
+}
+
+QString HTMLExporter::createFooter()
+{
+ if ( m_error )
+ return QString::null;
+
+ return "</body></html>";
+}
+
+void HTMLExporter::storePhoto( const Recipe &recipe )
+{
+ QImage image;
+ QString photo_name;
+ if ( recipe.photo.isNull() ) {
+ image = QImage( defaultPhoto );
+ photo_name = "default_photo";
+ }
+ else {
+ image = recipe.photo.convertToImage();
+ photo_name = QString::number(recipe.recipeID);
+ }
+
+ QPixmap pm = image;//image.smoothScale( phwidth, 0, QImage::ScaleMax );
+
+ QFileInfo fi(fileName());
+ QString photo_path = fi.dirPath(true) + "/" + fi.baseName() + "_photos/" + photo_name + ".png";
+ if ( !QFile::exists( photo_path ) ) {
+ pm.save( photo_path, "PNG" );
+ }
+}
+
+QString HTMLExporter::HTMLIfVisible( const QString &name, const QString &html )
+{
+ QMap<QString,bool>::const_iterator it = m_visibilityMap.find(name);
+ if ( it == m_visibilityMap.end() || it.data() == true )
+ return html;
+ else
+ return QString::null;
+}
+
+void HTMLExporter::populateTemplate( const Recipe &recipe, QString &content )
+{
+ KConfig * config = KGlobal::config();
+
+ //=======================TITLE======================//
+ content = content.replace("**TITLE**",HTMLIfVisible("title",recipe.title));
+
+ //=======================INSTRUCTIONS======================//
+ QString instr_html = QStyleSheet::escape( recipe.instructions );
+ instr_html.replace( "\n", "<br />" );
+ content = content.replace( "**INSTRUCTIONS**", HTMLIfVisible("instructions",instr_html) );
+
+ //=======================SERVINGS======================//
+ QString yield_html = QString( "<b>%1: </b>%2" ).arg( i18n( "Yield" ) ).arg( recipe.yield.toString() );
+ content = content.replace( "**YIELD**", HTMLIfVisible("yield",yield_html) );
+
+ //=======================PREP TIME======================//
+ QString preptime_html;
+ if ( !recipe.prepTime.isNull() && recipe.prepTime.isValid() )
+ preptime_html = QString( "<b>%1: </b>%2" ).arg( i18n( "Preparation Time" ) ).arg( recipe.prepTime.toString( "h:mm" ) );
+ content = content.replace( "**PREP_TIME**", HTMLIfVisible("prep_time",preptime_html) );
+
+ //========================PHOTO========================//
+ QString photo_name;
+ if ( recipe.photo.isNull() )
+ photo_name = "default_photo";
+ else
+ photo_name = QString::number(recipe.recipeID);
+
+ QFileInfo fi(fileName());
+ QString image_url = fi.baseName() + "_photos/" + escape( photo_name ) + ".png";
+ image_url = KURL::encode_string( image_url );
+ content = content.replace( "**PHOTO**", HTMLIfVisible("photo",image_url) );
+
+ //=======================AUTHORS======================//
+ QString authors_html;
+
+ int counter = 0;
+ for ( ElementList::const_iterator author_it = recipe.authorList.begin(); author_it != recipe.authorList.end(); ++author_it ) {
+ if ( counter )
+ authors_html += ", ";
+ authors_html += QStyleSheet::escape( ( *author_it ).name );
+ counter++;
+ }
+ if ( !authors_html.isEmpty() )
+ authors_html.prepend( QString( "<b>%1: </b>" ).arg( i18n( "Authors" ) ) );
+ content = content.replace( "**AUTHORS**", HTMLIfVisible("authors",authors_html) );
+
+ //=======================CATEGORIES======================//
+ QString categories_html;
+
+ counter = 0;
+ for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
+ if ( counter )
+ categories_html += ", ";
+ categories_html += QStyleSheet::escape( ( *cat_it ).name );
+ counter++;
+ }
+ if ( !categories_html.isEmpty() )
+ categories_html.prepend( QString( "<b>%1: </b>" ).arg( i18n( "Categories" ) ) );
+
+ content = content.replace( "**CATEGORIES**", HTMLIfVisible("categories",categories_html) );
+
+ //=======================INGREDIENTS======================//
+ QString ingredients_html;
+ config->setGroup( "Formatting" );
+
+ bool useAbbreviations = config->readBoolEntry("AbbreviateUnits");
+
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ QString ingredient_format = config->readEntry( "Ingredient", "%n%p: %a %u" );
+
+ QMap<QString,int>::const_iterator cols_it = m_columnsMap.find("ingredients");
+ int cols = 1;
+ if ( cols_it != m_columnsMap.end() )
+ cols = cols_it.data();
+ int per_col = recipe.ingList.count() / cols;
+ if ( recipe.ingList.count() % cols != 0 ) //round up if division is not exact
+ per_col++;
+
+ int count = 0;
+ IngredientList list_copy = recipe.ingList; //simple workaround until I fix iterating over the list dealing with groups
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ QString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+
+ bool loneHeader = false;
+ if ( count != 0 && count % per_col == 0 ) {
+ loneHeader = true;
+ if ( !group.isEmpty() )
+ ingredients_html += "</ul>";
+ ingredients_html.append("</ul></td><td valign=\"top\"><ul>");
+ if ( !group.isEmpty() )
+ ingredients_html += "<li style=\"page-break-after: avoid\">" + group + ":</li><ul>";
+ }
+ else {
+ if ( !group.isEmpty() )
+ ingredients_html += "<li style=\"page-break-after: avoid\">" + group + ":</li><ul>";
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it, ++count ) {
+ if ( count != 0 && count % per_col == 0 && !loneHeader ) {
+ if ( !group.isEmpty() )
+ ingredients_html += "</ul>";
+ ingredients_html.append("</ul></td><td valign=\"top\"><ul>");
+ if ( !group.isEmpty() )
+ ingredients_html += "<ul>";
+ }
+
+ QString amount_str = MixedNumber( ( *ing_it ).amount ).toString( number_format );
+
+ if ( (*ing_it).amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ( *ing_it ).amount + ( *ing_it ).amount_offset ).toString( number_format );
+ else if ( ( *ing_it ).amount <= 1e-10 )
+ amount_str = "";
+
+ QString unit = ( *ing_it ).units.determineName( ( *ing_it ).amount + ( *ing_it ).amount_offset, useAbbreviations );
+
+ QString tmp_format( ingredient_format );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%n" ) ), QStyleSheet::escape( ( *ing_it ).name ) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%a" ) ), amount_str );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%u" ) ), QStyleSheet::escape(unit) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%p" ) ), ( ( *ing_it ).prepMethodList.count() == 0 ) ?
+ QString::fromLatin1( "" ) : QString::fromLatin1( "; " ) + QStyleSheet::escape( ( *ing_it ).prepMethodList.join(",") ) );
+
+ if ( (*ing_it).substitutes.count() > 0 )
+ tmp_format += ", "+i18n("or");
+
+ ingredients_html += QString( "<li>%1</li>" ).arg( tmp_format );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ QString amount_str = MixedNumber( ( *sub_it ).amount ).toString( number_format );
+
+ if ( (*ing_it).amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ( *sub_it ).amount + ( *sub_it ).amount_offset ).toString( number_format );
+ else if ( ( *sub_it ).amount <= 1e-10 )
+ amount_str = "";
+
+ QString unit = ( *sub_it ).units.determineName( ( *sub_it ).amount + ( *sub_it ).amount_offset, config->readBoolEntry("AbbreviateUnits") );
+
+ QString tmp_format( ingredient_format );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%n" ) ), QStyleSheet::escape( ( *sub_it ).name ) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%a" ) ), amount_str );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%u" ) ), QStyleSheet::escape(unit) );
+ tmp_format.replace( QRegExp( QString::fromLatin1( "%p" ) ), ( ( *sub_it ).prepMethodList.count() == 0 ) ?
+ QString::fromLatin1( "" ) : QString::fromLatin1( "; " ) + QStyleSheet::escape( ( *sub_it ).prepMethodList.join(",") ) );
+
+ ++sub_it;
+ if ( sub_it != (*ing_it).substitutes.end() )
+ tmp_format += ", "+i18n("or");
+ ingredients_html += QString( "<li>%1</li>" ).arg( tmp_format );
+ }
+ }
+
+ if ( !group.isEmpty() )
+ ingredients_html += "</ul>";
+ }
+ if ( !ingredients_html.isEmpty() ) {
+ ingredients_html.prepend( "<table><tr><td valign=\"top\"><ul>" );
+ ingredients_html.append( "</ul></td></tr></table>" );
+ }
+ content = content.replace( "**INGREDIENTS**", HTMLIfVisible("ingredients",ingredients_html) );
+
+ //=======================PROPERTIES======================//
+ QString properties_html;
+
+ QStringList hiddenList = config->readListEntry("HiddenProperties");
+ IngredientPropertyList visibleProperties;
+ for ( IngredientPropertyList::const_iterator prop_it = recipe.properties.begin(); prop_it != recipe.properties.end(); ++prop_it ) {
+ if ( hiddenList.find((*prop_it).name) == hiddenList.end() )
+ visibleProperties.append( *prop_it );
+ }
+
+ cols_it = m_columnsMap.find("properties");
+ cols = 1;
+ if ( cols_it != m_columnsMap.end() )
+ cols = cols_it.data();
+ per_col = visibleProperties.count() / cols;
+ if ( visibleProperties.count() % cols != 0 ) //round up if division is not exact
+ per_col++;
+
+ count = 0;
+ for ( IngredientPropertyList::const_iterator prop_it = visibleProperties.begin(); prop_it != visibleProperties.end(); ++prop_it ) {
+ if ( count != 0 && count % per_col == 0 )
+ properties_html.append("</ul></td><td valign=\"top\"><ul>");
+
+ // if the amount given is <0, it means the property calculator found that the property was undefined for some ingredients, so the amount will be actually bigger
+
+ QString amount_str;
+
+ double prop_amount = (*prop_it).amount;
+ if ( prop_amount > 0 ) { //TODO: make the precision configuratble
+ prop_amount = double( qRound( prop_amount * 10.0 ) ) / 10.0; //not a "chemistry experiment" ;) Let's only have one decimal place
+ amount_str = beautify( KGlobal::locale() ->formatNumber( prop_amount, 5 ) );
+ }
+ else
+ amount_str = "0";
+
+ properties_html += QString( "<li>%1: <nobr>%2 %3</nobr></li>" )
+ .arg( QStyleSheet::escape( (*prop_it).name ) )
+ .arg( amount_str )
+ .arg( QStyleSheet::escape( (*prop_it).units ) );
+
+ ++count;
+ }
+
+ if ( !properties_html.isEmpty() ) {
+ properties_html.prepend( "<table><tr><td valign=\"top\"><ul>" );
+ properties_html.append( "</ul></td></tr></table>" );
+ }
+ content = content.replace( "**PROPERTIES**", HTMLIfVisible("properties",properties_html) );
+
+ //=======================RATINGS======================//
+ QString ratings_html;
+ if ( recipe.ratingList.count() > 0 )
+ ratings_html += QString("<b>%1:</b>").arg(i18n("Ratings"));
+
+ int rating_total = 0;
+ double rating_sum = 0;
+ for ( RatingList::const_iterator rating_it = recipe.ratingList.begin(); rating_it != recipe.ratingList.end(); ++rating_it ) {
+ ratings_html += "<hr />";
+
+ if ( !( *rating_it ).rater.isEmpty() )
+ ratings_html += "<p><b>"+( *rating_it ).rater+"</b></p>";
+
+ if ( (*rating_it).ratingCriteriaList.count() > 0 )
+ ratings_html += "<table>";
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ QString image_url = fi.baseName() + "_photos/" + QString::number((*rc_it).stars) + "-stars.png";
+ image_url = KURL::encode_string( image_url );
+ ratings_html += "<tr><td>"+(*rc_it).name+":</td><td><img src=\""+image_url+"\" /></td></tr>";
+ if ( !QFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
+ QPixmap starPixmap = Rating::starsPixmap((*rc_it).stars,true);
+ starPixmap.save( fi.dirPath(true) + "/" + image_url, "PNG" );
+ }
+
+ rating_total++;
+ rating_sum += (*rc_it).stars;
+ }
+ if ( (*rating_it).ratingCriteriaList.count() > 0 )
+ ratings_html += "</table>";
+
+ if ( !( *rating_it ).comment.isEmpty() )
+ ratings_html += "<p><i>"+( *rating_it ).comment+"</i></p>";
+ }
+ content = content.replace( "**RATINGS**", HTMLIfVisible("ratings",ratings_html) );
+
+ QString overall_html;
+ if ( rating_total > 0 ) {
+ double average = int(2*rating_sum/rating_total)/2;
+ overall_html += QString("<b>%1:</b>").arg(i18n("Overall Rating"));
+ QString image_url = fi.baseName() + "_photos/" + QString::number(average) + "-stars.png";
+ image_url = KURL::encode_string( image_url );
+ overall_html += "<img src=\""+image_url+"\" />";
+ if ( !QFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
+ QPixmap starPixmap = Rating::starsPixmap(average,true);
+ starPixmap.save( fi.dirPath(true) + "/" + image_url, "PNG" );
+ }
+ }
+ content = content.replace( "**OVERALL_RATING**", HTMLIfVisible("overall_rating",overall_html) );
+}
+
+void HTMLExporter::removeHTMLFiles( const QString &filename, int recipe_id )
+{
+ QValueList<int> id;
+ id << recipe_id;
+ removeHTMLFiles( filename, id );
+}
+
+void HTMLExporter::removeHTMLFiles( const QString &filename, const QValueList<int> &recipe_ids )
+{
+ //remove HTML file
+ QFile old_file( filename + ".html" );
+ if ( old_file.exists() )
+ old_file.remove();
+
+ //remove photos
+ for ( QValueList<int>::const_iterator it = recipe_ids.begin(); it != recipe_ids.end(); ++it ) {
+ QFile photo( filename + "_photos/" + QString::number(*it) + ".png" );
+ if ( photo.exists() )
+ photo.remove(); //remove photos in directory before removing it
+ }
+
+ //take care of the default photo
+ QFile photo( filename + "_photos/default_photo.png" );
+ if ( photo.exists() ) photo.remove();
+
+ //remove photo directory
+ QDir photo_dir;
+ photo_dir.rmdir( filename + "_photos" );
+
+ for ( double d = 0.5; d < 5.5; d += 0.5 ) {
+ if ( QFile::exists(filename+"_photos/"+QString::number(d)+"-stars.png") ) photo.remove(filename+"_photos/"+QString::number(d)+"-stars.png");
+ }
+}
+
+QString HTMLExporter::escape( const QString & str )
+{
+ QString tmp( str );
+ return tmp.replace( '/', "_" );
+}
diff --git a/krecipes/src/exporters/htmlexporter.h b/krecipes/src/exporters/htmlexporter.h
new file mode 100644
index 0000000..158869e
--- /dev/null
+++ b/krecipes/src/exporters/htmlexporter.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+* Copyright (C) 2003-2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef HTMLEXPORTER_H
+#define HTMLEXPORTER_H
+
+#include <qdom.h>
+#include <qmap.h>
+
+#include "baseexporter.h"
+#include "klomanager.h"
+
+class RecipeDB;
+class KProgress;
+
+/**
+ * Exports a given recipe list as HTML
+ * @author Jason Kivlighn
+ */
+class HTMLExporter : public BaseExporter, protected KLOManager
+{
+public:
+ HTMLExporter( const QString&, const QString& );
+ virtual ~HTMLExporter();
+
+ virtual int supportedItems() const;
+
+ static void removeHTMLFiles( const QString &filename, int recipe_id );
+ static void removeHTMLFiles( const QString &filename, const QValueList<int> &recipe_ids );
+
+ void setTemplate( const QString &filename );
+ void setStyle( const QString &filename );
+
+protected:
+ QString createContent( const Recipe& recipe );
+ virtual QString createContent( const RecipeList & );
+ virtual QString createHeader( const RecipeList & );
+ virtual QString createFooter();
+
+ virtual int progressInterval() const { return 1; }
+
+ virtual void loadBackgroundColor( const QString &obj, const QColor& );
+ virtual void loadFont( const QString &obj, const QFont& );
+ virtual void loadTextColor( const QString &obj, const QColor& );
+ virtual void loadVisibility( const QString &obj, bool );
+ virtual void loadAlignment( const QString &obj, int );
+ virtual void loadBorder( const QString &obj, const KreBorder& );
+ virtual void loadColumns( const QString & obj, int cols );
+
+ virtual void beginObject( const QString &obj );
+ virtual void endObject();
+
+ static QString escape( const QString & );
+
+ QString m_templateContent;
+
+private:
+ void storePhoto( const Recipe &recipe );
+ void populateTemplate( const Recipe &recipe, QString &content );
+ void replaceIfVisible( QString &content, const QString &name, const QString &html );
+ QString HTMLIfVisible( const QString &name, const QString &html );
+
+ QString m_layoutFilename;
+ QString m_templateFilename;
+ QString m_cachedCSS;
+ QMap<QString,bool> m_visibilityMap;
+ QMap<QString,int> m_columnsMap;
+ bool m_error;
+};
+
+#endif //HTMLEXPORTER_H
diff --git a/krecipes/src/exporters/kreexporter.cpp b/krecipes/src/exporters/kreexporter.cpp
new file mode 100644
index 0000000..5ed1b65
--- /dev/null
+++ b/krecipes/src/exporters/kreexporter.cpp
@@ -0,0 +1,265 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "kreexporter.h"
+
+#include <qfile.h>
+#include <qstylesheet.h>
+#include <qbuffer.h>
+#include <qimage.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmdcodec.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+#include "backends/recipedb.h"
+
+KreExporter::KreExporter( CategoryTree *_categories, const QString& filename, const QString &format ) :
+ BaseExporter( filename, format ), categories( _categories )
+{
+ if ( format == "*.kre" ) {
+ setCompressed(true);
+ }
+}
+
+
+KreExporter::~KreExporter()
+{
+ delete categories;
+}
+
+int KreExporter::supportedItems() const
+{
+ return RecipeDB::All;
+}
+
+int KreExporter::headerFlags() const
+{
+ return RecipeDB::Categories;
+}
+
+QString KreExporter::createHeader( const RecipeList& recipes )
+{
+ QString xml;
+
+ xml += "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ xml += "<krecipes version=\"" + krecipes_version() + "\" lang=\"" + ( KGlobal::locale() )->language() + "\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"krecipes.xsd\">\n";
+
+ createCategoryStructure( xml, recipes );
+
+ return xml;
+}
+
+QString KreExporter::createFooter()
+{
+ return "</krecipes>\n";
+}
+
+QString KreExporter::generateIngredient( const IngredientData &ing )
+{
+ QString xml;
+
+ xml += "<name>" + QStyleSheet::escape( ( ing ).name ) + "</name>\n";
+ xml += "<amount>";
+ if ( ing.amount_offset < 1e-10 ) {
+ xml += QString::number( ing.amount );
+ }
+ else {
+ xml += "<min>"+QString::number( ing.amount )+"</min>";
+ xml += "<max>"+QString::number( ing.amount + ing.amount_offset )+"</max>";
+ }
+ xml += "</amount>\n";
+ QString unit_str = ( ing.amount+ing.amount_offset > 1 ) ? ing.units.plural : ing.units.name;
+ xml += "<unit>" + QStyleSheet::escape( unit_str ) + "</unit>\n";
+ if ( ing.prepMethodList.count() > 0 )
+ xml += "<prep>" + QStyleSheet::escape( ing.prepMethodList.join(",") ) + "</prep>\n";
+
+ return xml;
+}
+
+//TODO: use QDOM (see recipemlexporter.cpp)?
+QString KreExporter::createContent( const RecipeList& recipes )
+{
+ QString xml;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+
+ xml += "<krecipes-recipe>\n";
+ xml += "<krecipes-description>\n";
+ xml += "<title>" + QStyleSheet::escape( ( *recipe_it ).title ) + "</title>\n";
+
+ for ( ElementList::const_iterator author_it = ( *recipe_it ).authorList.begin(); author_it != ( *recipe_it ).authorList.end(); ++author_it )
+ xml += "<author>" + QStyleSheet::escape( ( *author_it ).name ) + "</author>\n";
+
+ xml += "<pictures>\n";
+ if ( !( *recipe_it ).photo.isNull() ) {
+ xml += "<pic format=\"JPEG\" id=\"1\"><![CDATA["; //fixed id until we implement multiple photos ability
+ QByteArray data;
+ QBuffer buffer( data );
+ buffer.open( IO_WriteOnly );
+ QImageIO iio( &buffer, "JPEG" );
+ iio.setImage( ( *recipe_it ).photo.convertToImage() );
+ iio.write();
+ //( *recipe_it ).photo.save( &buffer, "JPEG" ); don't need QImageIO in QT 3.2
+
+ xml += KCodecs::base64Encode( data, true );
+
+ xml += "]]></pic>\n";
+ }
+ xml += "</pictures>\n";
+ xml += "<category>\n";
+
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it )
+ xml += "<cat>" + QStyleSheet::escape( ( *cat_it ).name ) + "</cat>\n";
+
+ xml += "</category>\n";
+ xml += "<yield>";
+ xml += "<amount>";
+ if ( ( *recipe_it ).yield.amount_offset < 1e-10 ) {
+ xml += QString::number( ( *recipe_it ).yield.amount );
+ }
+ else {
+ xml += "<min>"+QString::number( ( *recipe_it ).yield.amount )+"</min>";
+ xml += "<max>"+QString::number( ( *recipe_it ).yield.amount + ( *recipe_it ).yield.amount_offset )+"</max>";
+ }
+ xml += "</amount>";
+ xml += "<type>"+( *recipe_it ).yield.type+"</type>";
+ xml += "</yield>\n";
+ xml += "<preparation-time>";
+ xml += ( *recipe_it ).prepTime.toString( "hh:mm" );
+ xml += "</preparation-time>\n";
+ xml += "</krecipes-description>\n";
+ xml += "<krecipes-ingredients>\n";
+
+ IngredientList list_copy = ( *recipe_it ).ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ QString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+ if ( !group.isEmpty() )
+ xml += "<ingredient-group name=\"" + QStyleSheet::escape(group) + "\">\n";
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ xml += "<ingredient>\n";
+
+ xml += generateIngredient(*ing_it);
+
+ if ( (*ing_it).substitutes.count() > 0 ) {
+ xml += "<substitutes>\n";
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ xml += "<ingredient>\n";
+ xml += generateIngredient(*sub_it);
+ xml += "</ingredient>\n";
+ }
+ xml += "</substitutes>\n";
+ }
+
+ xml += "</ingredient>\n";
+ }
+
+ if ( !group.isEmpty() )
+ xml += "</ingredient-group>\n";
+ }
+
+ /// @todo add ingredient properties
+
+ xml += "</krecipes-ingredients>\n";
+ xml += "<krecipes-instructions>\n";
+ xml += QStyleSheet::escape( ( *recipe_it ).instructions );
+ xml += "</krecipes-instructions>\n";
+
+ //ratings
+ xml += "<krecipes-ratings>";
+ for ( RatingList::const_iterator rating_it = (*recipe_it).ratingList.begin(); rating_it != (*recipe_it).ratingList.end(); ++rating_it ) {
+ xml += "<rating>";
+ xml += "<comment>"+QStyleSheet::escape( ( *rating_it ).comment )+"</comment>";
+ xml += "<rater>"+QStyleSheet::escape( ( *rating_it ).rater )+"</rater>";
+
+ xml += "<criterion>";
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ xml += "<criteria>";
+ xml += "<name>"+(*rc_it).name+"</name>";
+ xml += "<stars>"+QString::number((*rc_it).stars)+"</stars>";
+ xml += "</criteria>";
+ }
+ xml += "</criterion>";
+ xml += "</rating>";
+ }
+ xml += "</krecipes-ratings>";
+
+ xml += "</krecipes-recipe>\n";
+ }
+
+ return xml;
+}
+
+void KreExporter::createCategoryStructure( QString &xml, const RecipeList &recipes )
+{
+ QValueList<int> categoriesUsed;
+ for ( RecipeList::const_iterator recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ if ( categoriesUsed.find( ( *cat_it ).id ) == categoriesUsed.end() )
+ categoriesUsed << ( *cat_it ).id;
+ }
+ }
+
+ if ( !categoriesUsed.empty() ) {
+ //only keep the relevant category structure
+ removeIfUnused( categoriesUsed, categories );
+
+ xml += "<krecipes-category-structure>\n";
+ writeCategoryStructure( xml, categories );
+ xml += "</krecipes-category-structure>\n";
+ }
+}
+
+bool KreExporter::removeIfUnused( const QValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show )
+{
+ for ( CategoryTree * it = parent->firstChild(); it; it = it->nextSibling() ) {
+ if ( cat_ids.find( it->category.id ) != cat_ids.end() ) {
+ parent_should_show = true;
+ removeIfUnused( cat_ids, it, true ); //still recurse, but doesn't affect 'parent'
+ }
+ else {
+ bool result = removeIfUnused( cat_ids, it );
+ if ( parent_should_show == false )
+ parent_should_show = result;
+ }
+ }
+
+ if ( !parent_should_show && parent->category.id != -1 ) {
+ //FIXME: CategoryTree is broken when deleting items
+ //delete parent;
+
+ parent->category.id = -2; //temporary workaround
+ }
+
+ return parent_should_show;
+}
+
+void KreExporter::writeCategoryStructure( QString &xml, const CategoryTree *categoryTree )
+{
+ if ( categoryTree->category.id != -2 ) {
+ if ( categoryTree->category.id != -1 )
+ xml += "<category name=\"" + QStyleSheet::escape( categoryTree->category.name ).replace("\"","&quot;") + "\">\n";
+
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ writeCategoryStructure( xml, child_it );
+ }
+
+ if ( categoryTree->category.id != -1 )
+ xml += "</category>\n";
+ }
+}
+
diff --git a/krecipes/src/exporters/kreexporter.h b/krecipes/src/exporters/kreexporter.h
new file mode 100644
index 0000000..9626933
--- /dev/null
+++ b/krecipes/src/exporters/kreexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KREEXPORTER_H
+#define KREEXPORTER_H
+
+#include "baseexporter.h"
+#include "datablocks/categorytree.h"
+
+class IngredientData;
+
+/**
+Export class for Krecipes native file format (.kre, .kreml)
+
+@author Cyril Bosselut and Jason Kivlighn
+*/
+class KreExporter : public BaseExporter
+{
+public:
+ KreExporter( CategoryTree *, const QString&, const QString& );
+ virtual ~KreExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual QString createContent( const RecipeList & );
+ virtual QString createHeader( const RecipeList & );
+ virtual QString createFooter();
+
+ virtual int headerFlags() const;
+
+private:
+ bool removeIfUnused( const QValueList<int> &cat_ids, CategoryTree *parent, bool parent_should_show = false );
+ void createCategoryStructure( QString &xml, const RecipeList &recipes );
+ void writeCategoryStructure( QString &xml, const CategoryTree *categoryTree );
+ QString generateIngredient( const IngredientData &ing );
+
+ CategoryTree *categories;
+};
+
+#endif
diff --git a/krecipes/src/exporters/mmfexporter.cpp b/krecipes/src/exporters/mmfexporter.cpp
new file mode 100644
index 0000000..c8125df
--- /dev/null
+++ b/krecipes/src/exporters/mmfexporter.cpp
@@ -0,0 +1,220 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "mmfexporter.h"
+
+#include <qregexp.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kapplication.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/mixednumber.h"
+#include "mmdata.h"
+
+MMFExporter::MMFExporter( const QString& filename, const QString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+MMFExporter::~MMFExporter()
+{}
+
+int MMFExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo ^ RecipeDB::Ratings ^ RecipeDB::Authors;
+}
+
+QString MMFExporter::createContent( const RecipeList& recipes )
+{
+ QString content;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ writeMMFHeader( content, *recipe_it );
+ content += "\n";
+ writeMMFIngredients( content, *recipe_it );
+ content += "\n";
+ writeMMFDirections( content, *recipe_it );
+ content += "\n";
+
+ content += "-----\n"; //end of recipe indicator
+ }
+
+ return content;
+}
+
+/* Header:
+ * Line 1 - contains five hyphens and "Meal-Master" somewhere in the line
+ * Line 2 - "Title:" followed by a blank space; maximum of 60 char
+ * Line 3 - "Categories:" followed by a blank space; Maximum of 5
+ * Line 4 - Numeric quantity representing the # of servings (1-9999)
+ */
+void MMFExporter::writeMMFHeader( QString &content, const Recipe &recipe )
+{
+ content += QString( "----- Exported by Krecipes v%1 [Meal-Master Export Format] -----\n\n" ).arg( krecipes_version() );
+
+ QString title = recipe.title;
+ title.truncate( 60 );
+ content += " Title: " + title + "\n";
+
+ int i = 0;
+ QStringList categories;
+ for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
+ i++;
+
+ if ( i == 6 )
+ break; //maximum of 5 categories
+ categories << ( *cat_it ).name;
+ }
+ QString cat_str = " Categories: " + categories.join( ", " );
+ cat_str.truncate( 67 );
+ content += cat_str + "\n";
+
+ content += " Servings: " + QString::number( QMIN( 9999, recipe.yield.amount ) ) + "\n"; //some yield info is lost here, but hey, that's the MM format
+}
+
+/* Ingredient lines:
+ * Positions 1-7 contains a numeric quantity
+ * Positions 9-10 Meal-Master unit of measure codes
+ * Positions 12-39 contain text for an ingredient name, or a "-"
+ * in position 12 and text in positions 13-39 (the latter is a
+ * "continuation" line for the previous ingredient name)
+ */
+void MMFExporter::writeMMFIngredients( QString &content, const Recipe &recipe )
+{
+ //this format requires ingredients without a group to be written first
+ for ( IngredientList::const_iterator ing_it = recipe.ingList.begin(); ing_it != recipe.ingList.end(); ++ing_it ) {
+ if ( ( *ing_it ).groupID == -1 ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ QValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+
+ IngredientList list_copy = recipe.ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ if ( group_list[ 0 ].groupID == -1 ) //we already handled this group
+ continue;
+
+ QString group = group_list[ 0 ].group.left( 76 ); //just use the first's name... they're all the same
+ if ( !group.isEmpty() ) {
+ int length = group.length();
+ QString filler_lt = QString().fill( '-', ( 76 - length ) / 2 );
+ QString filler_rt = ( length % 2 ) ? QString().fill( '-', ( 76 - length ) / 2 + 1 ) : filler_lt;
+ content += filler_lt + group + filler_rt + "\n";
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ QValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+}
+
+void MMFExporter::writeSingleIngredient( QString &content, const Ingredient &ing, bool is_sub )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ //columns 1-7
+ if ( ing.amount > 0 )
+ content += MixedNumber( ing.amount ).toString( number_format, false ).rightJustify( 7, ' ', true ) + " ";
+ else
+ content += " ";
+
+ //columns 9-10
+ bool found_short_form = false;
+ for ( int i = 0; unit_info[ i ].short_form; i++ ) {
+ if ( unit_info[ i ].expanded_form == ing.units.name ||
+ unit_info[ i ].plural_expanded_form == ing.units.plural ||
+ unit_info[ i ].short_form == ing.units.name ) {
+ found_short_form = true;
+ content += QString( unit_info[ i ].short_form ).leftJustify( 2 ) + " ";
+ break;
+ }
+ }
+ if ( !found_short_form ) {
+ kdDebug() << "Warning: unable to find Meal-Master abbreviation for: " << ing.units.name << endl;
+ kdDebug() << " This ingredient (" << ing.name << ") will be exported without a unit" << endl;
+ content += " ";
+ }
+
+ //columns 12-39
+ QString ing_name( ing.name );
+ if ( ing.prepMethodList.count() > 0 )
+ ing_name += "; " + ing.prepMethodList.join(", ");
+
+ if ( is_sub )
+ ing_name += ", or";
+
+ if ( !found_short_form )
+ ing_name.prepend( ( ing.amount > 1 ? ing.units.plural : ing.units.name ) + " " );
+
+ //try and split the ingredient on a word boundry
+ int split_index;
+ if ( ing_name.length() > 28 ) {
+ split_index = ing_name.left(28).findRev(" ")+1;
+ if ( split_index == 0 )
+ split_index = 28;
+ }
+ else
+ split_index = 28;
+
+ content += ing_name.left(split_index) + "\n";
+
+ for ( unsigned int i = 0; i < ( ing_name.length() - 1 ) / 28; i++ ) //if longer than 28 chars, continue on next line(s)
+ content += " -" + ing_name.mid( 28 * ( i ) + split_index, 28 ) + "\n";
+}
+
+void MMFExporter::writeMMFDirections( QString &content, const Recipe &recipe )
+{
+ QStringList lines = QStringList::split("\n",recipe.instructions,true);
+ for ( QStringList::const_iterator it = lines.begin(); it != lines.end(); ++it ) {
+ content += wrapText( *it, 80 ).join( "\n" ) + "\n";
+ }
+}
+
+QStringList MMFExporter::wrapText( const QString& str, int at ) const
+{
+ QStringList ret;
+ QString copy( str );
+ bool stop = false;
+ while ( !stop ) {
+ QString line( copy.left( at ) );
+ if ( line.length() >= copy.length() )
+ stop = true;
+ else {
+ QRegExp rxp( "(\\s\\S*)$", false ); // last word in the new line
+ rxp.setMinimal( true ); // one word, only one word, please
+ line = line.replace( rxp, "" ); // remove last word
+ }
+ copy = copy.remove( 0, line.length() );
+ line = line.stripWhiteSpace();
+ line.prepend( " " ); // indent line by one char
+ ret << line; // output of current line
+ }
+
+ return ret;
+}
diff --git a/krecipes/src/exporters/mmfexporter.h b/krecipes/src/exporters/mmfexporter.h
new file mode 100644
index 0000000..5b50f4d
--- /dev/null
+++ b/krecipes/src/exporters/mmfexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef MMFEXPORTER_H
+#define MMFEXPORTER_H
+
+#include "baseexporter.h"
+
+/**
+ * Export class for the Meal-Master file format
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * -Recipe photo
+ * -Authors
+ * -5 category maximum
+ * -Title is limited to 60 characters
+ * -Servings are limited to the range of 0-9999
+ * -Units are limited: If a given unit does not have a
+ * corresponding MM abbrev., otherwise it will be
+ * exported without a unit.
+ */
+class MMFExporter : public BaseExporter
+{
+public:
+ MMFExporter( const QString&, const QString& );
+ virtual ~MMFExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual QString createContent( const RecipeList & );
+
+private:
+ void writeMMFHeader( QString &content, const Recipe &recipe );
+ void writeMMFIngredients( QString &content, const Recipe &recipe );
+ void writeSingleIngredient( QString &content, const Ingredient &ing, bool is_sub = false );
+ void writeMMFDirections( QString &content, const Recipe &recipe );
+
+ QStringList wrapText( const QString& str, int at ) const;
+};
+
+#endif //MMFEXPORTER_H
diff --git a/krecipes/src/exporters/plaintextexporter.cpp b/krecipes/src/exporters/plaintextexporter.cpp
new file mode 100644
index 0000000..3fa6a57
--- /dev/null
+++ b/krecipes/src/exporters/plaintextexporter.cpp
@@ -0,0 +1,176 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "plaintextexporter.h"
+
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#include "backends/recipedb.h"
+
+PlainTextExporter::PlainTextExporter( const QString& filename, const QString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+PlainTextExporter::~PlainTextExporter()
+{}
+
+int PlainTextExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo;
+}
+
+QString PlainTextExporter::generateIngredient( const IngredientData &ing, MixedNumber::Format number_format )
+{
+ KConfig *config = KGlobal::config();
+
+ QString content;
+
+ QString amount_str = MixedNumber( ing.amount ).toString( number_format );
+
+ if ( ing.amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ing.amount + ing.amount_offset ).toString( number_format );
+ else if ( ing.amount <= 1e-10 )
+ amount_str = "";
+
+ content += amount_str;
+ if ( !amount_str.isEmpty() )
+ content += " ";
+
+ QString unit_str = ing.units.determineName( ing.amount + ing.amount_offset, config->readBoolEntry("AbbreviateUnits") );
+
+ content += unit_str;
+ if ( !unit_str.isEmpty() )
+ content += " ";
+
+ content += ing.name;
+
+ if ( ing.prepMethodList.count() > 0 )
+ content += "; "+ing.prepMethodList.join(", ");
+
+ return content;
+}
+
+
+QString PlainTextExporter::createContent( const RecipeList& recipes )
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Formatting" );
+
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ QString content;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ content += ( *recipe_it ).title + "\n\n";
+
+ if ( ( *recipe_it ).authorList.count() > 0 ) {
+ content += QString("%1: ").arg(i18n("Authors"));
+ content += ( *recipe_it ).authorList.join(", ");
+ content += "\n";
+ }
+
+ if ( ( *recipe_it ).categoryList.count() > 0 ) {
+ content += QString("%1: ").arg(i18n("Categories"));
+ content += ( *recipe_it ).categoryList.join(", ");
+ content += "\n";
+ }
+
+ if ( ( *recipe_it ).yield.amount > 0 ) {
+ content += QString("%1: ").arg(i18n("Yields"));
+ content += ( *recipe_it ).yield.toString();
+ content += "\n";
+ }
+
+ if ( !( *recipe_it ).prepTime.isNull() ) {
+ content += QString("%1: ").arg(i18n("Preparation Time"));
+ content += ( *recipe_it ).prepTime.toString( "hh:mm" );
+ content += "\n";
+ }
+
+ content += "\n";
+
+ IngredientList list_copy = ( *recipe_it ).ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ QString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+ if ( !group.isEmpty() )
+ content += group + ":\n";
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ if ( !group.isEmpty() )
+ content += " ";
+
+ content += generateIngredient(*ing_it,number_format);
+
+ if ( (*ing_it).substitutes.count() > 0 )
+ content += ", "+i18n("or");
+ content += "\n";
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ if ( !group.isEmpty() )
+ content += " ";
+
+ content += generateIngredient(*sub_it,number_format);
+ sub_it++;
+ if ( sub_it != (*ing_it).substitutes.end() )
+ content += ", "+i18n("or");
+ content += "\n";
+ }
+ }
+ }
+
+ content += "\n";
+
+ /// @todo add ingredient properties
+
+ content += ( *recipe_it ).instructions;
+
+ content += "\n\n";
+
+ if ( (*recipe_it).ratingList.count() > 0 )
+ content += "----------"+i18n("Ratings")+"----------\n";
+
+ for ( RatingList::const_iterator rating_it = (*recipe_it).ratingList.begin(); rating_it != (*recipe_it).ratingList.end(); ++rating_it ) {
+ if ( !( *rating_it ).rater.isEmpty() )
+ content += " "+( *rating_it ).rater+"\n";
+
+ if ( (*rating_it).ratingCriteriaList.size() > 0 )
+ content += "\n";
+
+ for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ //FIXME: This is an ugly hack, but I don't know how else to be i18n friendly (if this is even that)
+ // and still be able to display the amount as a fraction
+ QString starsTrans = i18n("1 star","%n stars",qRound((*rc_it).stars));
+ starsTrans.replace(QString::number(qRound((*rc_it).stars)),MixedNumber((*rc_it).stars).toString());
+
+ content += " "+(*rc_it).name+": "+starsTrans+"\n";
+ }
+
+ if ( (*rating_it).ratingCriteriaList.size() > 0 )
+ content += "\n";
+
+ if ( !( *rating_it ).comment.isEmpty() )
+ content += " "+( *rating_it ).comment+"\n";
+
+ content += "\n";
+ }
+
+ if ( (*recipe_it).ratingList.size() > 0 )
+ content += "\n";
+
+ content += "-----\n\n"; //end of recipe indicator
+ }
+
+ return content;
+}
+
diff --git a/krecipes/src/exporters/plaintextexporter.h b/krecipes/src/exporters/plaintextexporter.h
new file mode 100644
index 0000000..f38e2ca
--- /dev/null
+++ b/krecipes/src/exporters/plaintextexporter.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PLAINTEXTEXPORTER_H
+#define PLAINTEXTEXPORTER_H
+
+#include "baseexporter.h"
+
+#include "datablocks/mixednumber.h"
+
+class IngredientData;
+
+/**
+ * Export class to export recipes into text format.
+ * Recipes exported with this class are not meant to be imported back
+ * into Krecipes or any other program.
+ *
+ * @author Jason Kivlighn
+ */
+class PlainTextExporter : public BaseExporter
+{
+public:
+ PlainTextExporter( const QString&, const QString& );
+ virtual ~PlainTextExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual QString createContent( const RecipeList & );
+
+private:
+ QString generateIngredient( const IngredientData &ing, MixedNumber::Format number_format );
+};
+
+#endif //PLAINTEXTEXPORTER_H
diff --git a/krecipes/src/exporters/recipemlexporter.cpp b/krecipes/src/exporters/recipemlexporter.cpp
new file mode 100644
index 0000000..4a6bfc9
--- /dev/null
+++ b/krecipes/src/exporters/recipemlexporter.cpp
@@ -0,0 +1,195 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipemlexporter.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include "backends/recipedb.h"
+
+RecipeMLExporter::RecipeMLExporter( const QString& filename, const QString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+RecipeMLExporter::~RecipeMLExporter()
+{}
+
+int RecipeMLExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo ^ RecipeDB::Ratings;
+}
+
+QString RecipeMLExporter::createHeader( const RecipeList& )
+{
+ QString xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
+ xml += "<!DOCTYPE recipeml PUBLIC \"-//FormatData//DTD RecipeML 0.5//EN\" \
+ \"http://www.formatdata.com/recipeml/recipeml.dtd\">";
+ xml += "<recipeml version=\"0.5\" generator=\"Krecipes v"+krecipes_version()+"\">\n";
+ return xml;
+}
+
+QString RecipeMLExporter::createFooter()
+{
+ return "</recipeml>";
+}
+
+void RecipeMLExporter::createIngredient( QDomElement &ing_tag, const IngredientData &ing, QDomDocument &doc )
+{
+ QDomElement amt_tag = doc.createElement( "amt" );
+ ing_tag.appendChild( amt_tag );
+
+ QDomElement qty_tag = doc.createElement( "qty" );
+ amt_tag.appendChild( qty_tag );
+ if ( ing.amount_offset < 1e-10 )
+ qty_tag.appendChild( doc.createTextNode( QString::number( ing.amount ) ) );
+ else {
+ QDomElement range_tag = doc.createElement( "range" );
+ qty_tag.appendChild(range_tag);
+
+ QDomElement q1_tag = doc.createElement( "q1" );
+ q1_tag.appendChild( doc.createTextNode( QString::number( ing.amount ) ) );
+ QDomElement q2_tag = doc.createElement( "q2" );
+ q2_tag.appendChild( doc.createTextNode( QString::number( ing.amount + ing.amount_offset ) ) );
+
+ range_tag.appendChild(q1_tag);
+ range_tag.appendChild(q2_tag);
+ }
+
+ QDomElement unit_tag = doc.createElement( "unit" );
+ amt_tag.appendChild( unit_tag );
+ unit_tag.appendChild( doc.createTextNode( ( ing.amount > 1 ) ? ing.units.plural : ing.units.name ) );
+
+ QDomElement item_tag = doc.createElement( "item" );
+ item_tag.appendChild( doc.createTextNode( ing.name ) );
+ ing_tag.appendChild( item_tag );
+
+ QDomElement prep_tag = doc.createElement( "prep" );
+ prep_tag.appendChild( doc.createTextNode( ing.prepMethodList.join(",") ) );
+ ing_tag.appendChild( prep_tag );
+}
+
+QString RecipeMLExporter::createContent( const RecipeList& recipes )
+{
+ QDomDocument doc;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ QDomElement recipe_tag = doc.createElement( "recipe" );
+
+ doc.appendChild(recipe_tag);
+ //recipe_root.appendChild( recipe_tag ); //will append to either <menu> if exists or else <recipeml>
+
+ QDomElement head_tag = doc.createElement( "head" );
+ recipe_tag.appendChild( head_tag );
+
+ QDomElement title_tag = doc.createElement( "title" );
+ title_tag.appendChild( doc.createTextNode( ( *recipe_it ).title ) );
+ head_tag.appendChild( title_tag );
+
+ QDomElement source_tag = doc.createElement( "source" );
+ for ( ElementList::const_iterator author_it = ( *recipe_it ).authorList.begin(); author_it != ( *recipe_it ).authorList.end(); ++author_it ) {
+ QDomElement srcitem_tag = doc.createElement( "srcitem" );
+ srcitem_tag.appendChild( doc.createTextNode( ( *author_it ).name ) );
+ source_tag.appendChild( srcitem_tag );
+ }
+ head_tag.appendChild( source_tag );
+
+ QDomElement categories_tag = doc.createElement( "categories" );
+ for ( ElementList::const_iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != ( *recipe_it ).categoryList.end(); ++cat_it ) {
+ QDomElement cat_tag = doc.createElement( "cat" );
+ cat_tag.appendChild( doc.createTextNode( ( *cat_it ).name ) );
+ categories_tag.appendChild( cat_tag );
+ }
+ head_tag.appendChild( categories_tag );
+
+ QDomElement yield_tag = doc.createElement( "yield" );
+ if ( ( *recipe_it ).yield.amount_offset < 1e-10 )
+ yield_tag.appendChild( doc.createTextNode( QString::number( ( *recipe_it ).yield.amount ) ) );
+ else {
+ QDomElement range_tag = doc.createElement( "range" );
+ yield_tag.appendChild(range_tag);
+
+ QDomElement q1_tag = doc.createElement( "q1" );
+ q1_tag.appendChild( doc.createTextNode( QString::number(( *recipe_it ).yield.amount ) ) );
+ QDomElement q2_tag = doc.createElement( "q2" );
+ q2_tag.appendChild( doc.createTextNode( QString::number( ( *recipe_it ).yield.amount + ( *recipe_it ).yield.amount_offset ) ) );
+
+ range_tag.appendChild(q1_tag);
+ range_tag.appendChild(q2_tag);
+ }
+ if ( !( *recipe_it ).yield.type.isEmpty() ) {
+ QDomElement yield_unit_tag = doc.createElement( "unit" );
+ yield_unit_tag.appendChild( doc.createTextNode(( *recipe_it ).yield.type) );
+ yield_tag.appendChild( yield_unit_tag );
+ }
+
+ head_tag.appendChild( yield_tag );
+
+ if ( !( *recipe_it ).prepTime.isNull() ) {
+ QDomElement preptime_tag = doc.createElement( "preptime" );
+ head_tag.appendChild( preptime_tag );
+ preptime_tag.setAttribute( "type", i18n( "Total" ) );
+
+ QDomElement preptime_time_tag = doc.createElement( "time" );
+ preptime_tag.appendChild( preptime_time_tag );
+
+ QDomElement preptime_min_qty_tag = doc.createElement( "qty" );
+ preptime_time_tag.appendChild( preptime_min_qty_tag );
+ preptime_min_qty_tag.appendChild( doc.createTextNode( QString::number( ( *recipe_it ).prepTime.minute() + ( *recipe_it ).prepTime.hour() * 60 ) ) );
+
+ QDomElement preptime_min_unit_tag = doc.createElement( "timeunit" );
+ preptime_time_tag.appendChild( preptime_min_unit_tag );
+ preptime_min_unit_tag.appendChild( doc.createTextNode( "minutes" ) );
+ }
+
+ QDomElement ingredients_tag = doc.createElement( "ingredients" );
+ IngredientList list_copy = ( *recipe_it ).ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ QDomElement ing_root;
+
+ QString group = group_list[ 0 ].group; //just use the first's name... they're all the same
+ if ( !group.isEmpty() ) {
+ QDomElement ingdiv_tag = doc.createElement( "ing-div" );
+ QDomElement title_tag = doc.createElement( "title" );
+ title_tag.appendChild( doc.createTextNode( group ) );
+ ingdiv_tag.appendChild( title_tag );
+ ingredients_tag.appendChild( ingdiv_tag );
+ ing_root = ingdiv_tag;
+ }
+ else
+ ing_root = ingredients_tag;
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ QDomElement ing_tag = doc.createElement( "ing" );
+ ing_root.appendChild( ing_tag );
+
+ createIngredient( ing_tag, *ing_it, doc );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ QDomElement alt_ing_tag = doc.createElement( "alt-ing" );
+ ing_tag.appendChild( alt_ing_tag );
+ createIngredient( alt_ing_tag, *sub_it, doc );
+ }
+ }
+ }
+ recipe_tag.appendChild( ingredients_tag );
+
+ QDomElement directions_tag = doc.createElement( "directions" );
+ recipe_tag.appendChild( directions_tag );
+
+ QDomElement step_tag = doc.createElement( "step" ); //we've just got everything in one step
+ directions_tag.appendChild( step_tag );
+ step_tag.appendChild( doc.createTextNode( ( *recipe_it ).instructions ) );
+ }
+
+ return doc.toString();
+}
diff --git a/krecipes/src/exporters/recipemlexporter.h b/krecipes/src/exporters/recipemlexporter.h
new file mode 100644
index 0000000..b975c67
--- /dev/null
+++ b/krecipes/src/exporters/recipemlexporter.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEMLEXPORTER_H
+#define RECIPEMLEXPORTER_H
+
+#include "baseexporter.h"
+
+#include <qdom.h>
+
+class IngredientData;
+
+/**
+ * Export class for the RecipeML file format <http://www.formatdata.com/recipeml>
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * -Recipe photo
+ */
+class RecipeMLExporter : public BaseExporter
+{
+public:
+ RecipeMLExporter( const QString&, const QString& );
+ virtual ~RecipeMLExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual QString createContent( const RecipeList& );
+ virtual QString createHeader( const RecipeList& );
+ virtual QString createFooter();
+
+private:
+ void createIngredient( QDomElement &ing_tag, const IngredientData &, QDomDocument &doc );
+};
+
+#endif //RECIPEMLEXPORTER_H
diff --git a/krecipes/src/exporters/rezkonvexporter.cpp b/krecipes/src/exporters/rezkonvexporter.cpp
new file mode 100644
index 0000000..e714897
--- /dev/null
+++ b/krecipes/src/exporters/rezkonvexporter.cpp
@@ -0,0 +1,322 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "rezkonvexporter.h"
+
+#include <qregexp.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kapplication.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/mixednumber.h"
+
+struct translate_unit_info
+{
+ const char *english;
+ const char *english_plural;
+ const char *german;
+ const char *german_plural;
+};
+
+static translate_unit_info translate_units[] = {
+ {"g", "g", "Gramm", "Gramms" },
+ {"ml", "ml", "ml", "ml"},
+ {"l", "l", "Ltr.", "Ltr."},
+ {"kg", "kg", "kg", "kg"},
+ {"mg", "mg", "mg", "mg"},
+ {"teaspoon", "teaspoons", "Teel.", "Teel."},
+ {"t.", "t.", "Teel.", "Teel."},
+ {"t", "t", "Teel.", "Teel."},
+ {"tsp.", "tsp.", "Teel.", "Teel."},
+ {"tsp", "tsp", "Teel.", "Teel."},
+ {"tablespoon", "tablespoons", "Essl.", "Essl."},
+ {"tbsp.", "tbsp.", "Essl.", "Essl."},
+ {"tbsp", "tbsp", "Essl.", "Essl."},
+ {"T", "T", "Essl.", "Essl."},
+ {"T.", "T.", "Essl.", "Essl."},
+ {"cup", "cups", "Tasse", "Tassen"},
+ {"c.", "c.", "Tasse", "Tassen"},
+ {"can", "cans", "Dose", "Dosen"},
+ {"drop", "drops", "Tropfen", "Tropfen"},
+ {"large", "large", "gro", "gro"},
+ {"medium", "medium", "mittl.", "mittl."},
+ {"small", "small", "klein.", "klein."},
+ {"pinch", "pinches", "Prise", "Prisen"},
+ {"package", "packages", "Pack.", "Pack."},
+ {"bunch", "bunches", "Bund.", "Bunde"},
+ {"stem", "stems", "Strange", "Strangen"},
+ {"twig", "twigs", "Zweig", "Zweige"},
+ {"tip of a knife", "tips of a knife", "Messersp.", "Messersp."},
+ {"sheet", "sheets", "Blatt", "Bltter"},
+ {"handful", "handfuls", "Handvoll", "Handvoll"},
+ {"head", "heads", "Kopf", "Kpfe"},
+ {"slice", "slices", "Scheibe", "Sheiben"},
+ {"some", "some", "Einige", "Einige"},
+ {"a little", "a little", "Etwas", "Etwas"},
+ {"little can", "little cans", "Dschen", "Dschen"},
+ {"glass", "glasses", "Glas", "Glser"},
+ {"piece", "pieces", "Stck", "Stck"},
+ {"pot", "pots", "Topf", "Topf"},
+ {"generous", "generous", "Reichlich", "Reichlich"},
+ {"dash", "dashes", "Spritzer", "Spritzer"},
+ {"clove", "cloves", "Zehe", "Zehen"},
+ {"slice", "slices", "Platte", "Platten"},
+ {"shot", "shots", "Schuss", "Schuss"},
+ {"peduncle", "peduncles", "Stiel", "Stiele"},
+ {"heaping teaspoon", "heaping teaspoons", "geh. TL", "geh. TL"},
+ {"heaping tsp.", "heaping tsp.", "geh. TL", "geh. TL"},
+ {"heaping tsp.", "heaping tsp.", "geh. TL", "geh. TL"},
+ {"heaping tablespoon", "heaping tablespoon", "geh. EL", "geh. EL"},
+ {"heaping tbsp.", "heaping tbsp.", "geh. EL", "geh. EL"},
+#if 0
+ {"pound", "pounds", "", ""},
+ {"lb.", "lbs.", "", ""},
+ {"ounce", "ounces", "", ""},
+ {"oz.", "oz.", "", ""},
+#endif
+ {"", "", "", ""},
+ { 0, 0, 0, 0 }
+ };
+
+RezkonvExporter::RezkonvExporter( const QString& filename, const QString& format ) :
+ BaseExporter( filename, format )
+{}
+
+
+RezkonvExporter::~RezkonvExporter()
+{}
+
+int RezkonvExporter::supportedItems() const
+{
+ return RecipeDB::All ^ RecipeDB::Photo ^ RecipeDB::Ratings;
+}
+
+QString RezkonvExporter::createContent( const RecipeList& recipes )
+{
+ QString content;
+
+ RecipeList::const_iterator recipe_it;
+ for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
+ writeHeader( content, *recipe_it );
+ content += "\n";
+ writeIngredients( content, *recipe_it );
+ content += "\n";
+ writeDirections( content, *recipe_it );
+ content += "\n";
+
+ content += "=====\n\n"; //end of recipe indicator
+ }
+
+ return content;
+}
+
+/* Header:
+ * Line 1 - contains five hyphens and "Meal-Master" somewhere in the line
+ * Line 2 - "Title:" followed by a blank space; maximum of 60 char
+ * Line 3 - "Categories:" followed by a blank space; Maximum of 5
+ * Line 4 - Numeric quantity representing the # of servings (1-9999)
+ */
+void RezkonvExporter::writeHeader( QString &content, const Recipe &recipe )
+{
+ content += QString( "===== Exported by Krecipes v%1 [REZKONV Export Format] =====\n\n" ).arg( krecipes_version() );
+
+ QString title = recipe.title;
+ title.truncate( 60 );
+ content += QString("Titel: ").rightJustify( 13, ' ', true) + title + "\n";
+
+ int i = 0;
+ QStringList categories;
+ for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
+ i++;
+
+ if ( i == 6 )
+ break; //maximum of 5 categories
+ categories << ( *cat_it ).name;
+ }
+ QString cat_str = QString("Kategorien: ").rightJustify( 13, ' ', true) + categories.join( ", " );
+ cat_str.truncate( 67 );
+ content += cat_str + "\n";
+
+ content += QString("Menge: ").rightJustify( 13, ' ', true) + recipe.yield.toString() + "\n";
+}
+
+/* Ingredient lines:
+ * Positions 1-7 contains a numeric quantity
+ * Positions 9-10 Meal-Master unit of measure codes
+ * Positions 12-39 contain text for an ingredient name, or a "-"
+ * in position 12 and text in positions 13-39 (the latter is a
+ * "continuation" line for the previous ingredient name)
+ */
+void RezkonvExporter::writeIngredients( QString &content, const Recipe &recipe )
+{
+ //this format requires ingredients without a group to be written first
+ for ( IngredientList::const_iterator ing_it = recipe.ingList.begin(); ing_it != recipe.ingList.end(); ++ing_it ) {
+ if ( ( *ing_it ).groupID == -1 ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ QValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+
+ IngredientList list_copy = recipe.ingList;
+ for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
+ if ( group_list[ 0 ].groupID == -1 ) //we already handled this group
+ continue;
+
+ QString group = group_list[ 0 ].group.left( 76 ); //just use the first's name... they're all the same
+ if ( !group.isEmpty() ) {
+ int length = group.length();
+ QString filler_lt = QString().fill( '=', ( 76 - length ) / 2 );
+ QString filler_rt = ( length % 2 ) ? QString().fill( '=', ( 76 - length ) / 2 + 1 ) : filler_lt;
+ content += filler_lt + group + filler_rt + "\n";
+ }
+
+ for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it ) {
+ writeSingleIngredient( content, *ing_it, (*ing_it).substitutes.count() > 0 );
+
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
+ QValueList<IngredientData>::const_iterator save_it = sub_it;
+
+ ++sub_it;
+ writeSingleIngredient( content, *save_it, sub_it != (*ing_it).substitutes.end() );
+ }
+ }
+ }
+
+ QString authorLines;
+ if ( recipe.authorList.count() > 0 ) {
+ content += "============================== QUELLE ==============================\n";
+ authorLines = " "+(*recipe.authorList.begin()).name+"\n";
+ }
+ for ( ElementList::const_iterator author_it = ++recipe.authorList.begin(); author_it != recipe.authorList.end(); ++author_it ) {
+ authorLines += " -- ";
+ authorLines += (*author_it).name + "\n";
+ }
+ if ( !authorLines.isEmpty() )
+ authorLines += "\n";
+ content += authorLines;
+}
+
+void RezkonvExporter::writeSingleIngredient( QString &content, const IngredientData &ing, bool is_sub )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+ MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ //columns 1-7
+ if ( ing.amount > 1e-8 ) {
+ QString amount_str = MixedNumber( ing.amount ).toString( number_format, false );
+
+ if ( ing.amount_offset > 0 )
+ amount_str += "-"+MixedNumber( ing.amount + ing.amount_offset ).toString( number_format, false );
+
+ if ( amount_str.length() > 7 ) { //too long, let's try the other formatting
+ MixedNumber::Format other_format = (number_format == MixedNumber::DecimalFormat) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;
+
+ QString new_amount_str = MixedNumber( ing.amount ).toString( other_format, false );
+
+ if ( ing.amount_offset > 0 )
+ new_amount_str += "-"+MixedNumber( ing.amount + ing.amount_offset ).toString( other_format, false );
+
+ if (new_amount_str.length() > 7) { //still too long, use original formatting, but truncate it
+ amount_str = amount_str.left(7);
+ kdDebug()<<"Warning: Amount text too long, truncating"<<endl;
+ }
+ }
+ content += amount_str.rightJustify( 7, ' ', true ) + " ";
+ }
+ else
+ content += QString().fill(' ',7+1);
+
+ //columns 9-19
+ bool found_translation = false;
+ for ( int i = 0; translate_units[ i ].english; i++ ) {
+ if ( translate_units[ i ].english == ing.units.name || translate_units[ i ].english_plural == ing.units.plural || translate_units[ i ].german == ing.units.name || translate_units[ i ].german_plural == ing.units.plural ) {
+ found_translation = true;
+ QString unit;
+ if ( ing.amount > 1 )
+ unit += translate_units[i].german;
+ else
+ unit += translate_units[i].german_plural;
+ content += unit.leftJustify( 9 ) + " ";
+ break;
+ }
+ }
+ if ( !found_translation ) {
+ kdDebug() << "Warning: unable to find German translation for: " << ing.units.name << endl;
+ kdDebug() << " This ingredient (" << ing.name << ") will be exported without a unit" << endl;
+ content += QString().fill(' ',9+1);
+ }
+
+ //columns 21-70
+ QString ing_name( ing.name );
+ if ( ing.prepMethodList.count() > 0 )
+ ing_name += "; " + ing.prepMethodList.join(", ");
+
+ if ( is_sub )
+ ing_name += ", or"; //FIXME: what's 'or' in German?
+
+ if ( !found_translation )
+ ing_name.prepend( ( ing.amount > 1 ? ing.units.plural : ing.units.name ) + " " );
+
+ //try and split the ingredient on a word boundry
+ int split_index;
+ if ( ing_name.length() > 50 ) {
+ split_index = ing_name.left(50).findRev(" ")+1;
+ if ( split_index == 0 )
+ split_index = 50;
+ }
+ else
+ split_index = 50;
+
+ content += ing_name.left(split_index) + "\n";
+
+ for ( unsigned int i = 0; i < ( ing_name.length() - 1 ) / 50; i++ ) //if longer than 50 chars, continue on next line(s)
+ content += QString().fill(' ',(7+1)+(9+1)) + "-" + ing_name.mid( 50 * ( i ) + split_index, 50 ) + "\n";
+}
+
+void RezkonvExporter::writeDirections( QString &content, const Recipe &recipe )
+{
+ QStringList lines = QStringList::split("\n",recipe.instructions,true);
+ for ( QStringList::const_iterator it = lines.begin(); it != lines.end(); ++it ) {
+ content += wrapText( *it, 80 ).join( "\n" ) + "\n";
+ }
+}
+
+QStringList RezkonvExporter::wrapText( const QString& str, int at ) const
+{
+ QStringList ret;
+ QString copy( str );
+ bool stop = false;
+ while ( !stop ) {
+ QString line( copy.left( at ) );
+ if ( line.length() >= copy.length() )
+ stop = true;
+ else {
+ QRegExp rxp( "(\\s\\S*)$", false ); // last word in the new line
+ rxp.setMinimal( true ); // one word, only one word, please
+ line = line.replace( rxp, "" ); // remove last word
+ }
+ copy = copy.remove( 0, line.length() );
+ line = line.stripWhiteSpace();
+ line.prepend( " " ); // indent line by one char
+ ret << line; // output of current line
+ }
+
+ return ret;
+}
diff --git a/krecipes/src/exporters/rezkonvexporter.h b/krecipes/src/exporters/rezkonvexporter.h
new file mode 100644
index 0000000..230a1ba
--- /dev/null
+++ b/krecipes/src/exporters/rezkonvexporter.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef REZKONVEXPORTER_H
+#define REZKONVEXPORTER_H
+
+#include "baseexporter.h"
+
+/**
+ * Export class for the Rezkonv file format
+ * @author Jason Kivlighn
+ *
+ * Note: This format does not handle all the properties of recipes.
+ * Data lost in export to this format include:
+ * -Recipe photo
+ * -Authors
+ * -5 category maximum
+ * -Title is limited to 60 characters
+ * -Servings are limited to the range of 0-9999
+ * -Units are limited: If a given unit does not have a
+ * corresponding MM abbrev., otherwise it will be
+ * exported without a unit.
+ */
+class RezkonvExporter : public BaseExporter
+{
+public:
+ RezkonvExporter( const QString&, const QString& );
+ virtual ~RezkonvExporter();
+
+ virtual int supportedItems() const;
+
+protected:
+ virtual QString createContent( const RecipeList & );
+
+private:
+ void writeHeader( QString &content, const Recipe &recipe );
+ void writeIngredients( QString &content, const Recipe &recipe );
+ void writeSingleIngredient( QString &content, const IngredientData &ing, bool is_sub = false );
+ void writeDirections( QString &content, const Recipe &recipe );
+
+ QStringList wrapText( const QString& str, int at ) const;
+};
+
+#endif //REZKONVEXPORTER_H
diff --git a/krecipes/src/hi128-app-krecipes.png b/krecipes/src/hi128-app-krecipes.png
new file mode 100644
index 0000000..f98a185
--- /dev/null
+++ b/krecipes/src/hi128-app-krecipes.png
Binary files differ
diff --git a/krecipes/src/hi16-app-krecipes.png b/krecipes/src/hi16-app-krecipes.png
new file mode 100644
index 0000000..90671db
--- /dev/null
+++ b/krecipes/src/hi16-app-krecipes.png
Binary files differ
diff --git a/krecipes/src/hi22-app-krecipes.png b/krecipes/src/hi22-app-krecipes.png
new file mode 100644
index 0000000..d08e65b
--- /dev/null
+++ b/krecipes/src/hi22-app-krecipes.png
Binary files differ
diff --git a/krecipes/src/hi32-app-krecipes.png b/krecipes/src/hi32-app-krecipes.png
new file mode 100644
index 0000000..9b05028
--- /dev/null
+++ b/krecipes/src/hi32-app-krecipes.png
Binary files differ
diff --git a/krecipes/src/hi48-app-krecipes.png b/krecipes/src/hi48-app-krecipes.png
new file mode 100644
index 0000000..54d096e
--- /dev/null
+++ b/krecipes/src/hi48-app-krecipes.png
Binary files differ
diff --git a/krecipes/src/hi64-app-krecipes.png b/krecipes/src/hi64-app-krecipes.png
new file mode 100644
index 0000000..34bf989
--- /dev/null
+++ b/krecipes/src/hi64-app-krecipes.png
Binary files differ
diff --git a/krecipes/src/image.h b/krecipes/src/image.h
new file mode 100644
index 0000000..8558df8
--- /dev/null
+++ b/krecipes/src/image.h
@@ -0,0 +1,1123 @@
+#ifndef IMAGE_H
+#define IMAGE_H
+
+static const char * defaultPhoto[] =
+ {
+ "221 166 948 2",
+ " c None",
+ ". c #00001E",
+ "+ c #00001D",
+ "@ c #00001C",
+ "# c #00001B",
+ "$ c #00001A",
+ "% c #00001F",
+ "& c #000020",
+ "* c #000021",
+ "= c #000022",
+ "- c #1B1B25",
+ "; c #29292F",
+ "> c #353539",
+ ", c #37373B",
+ "' c #38383C",
+ ") c #414144",
+ "! c #404043",
+ "~ c #39393D",
+ "{ c #303035",
+ "] c #22222A",
+ "^ c #000023",
+ "/ c #353537",
+ "( c #4B4B4B",
+ "_ c #525252",
+ ": c #585858",
+ "< c #5B5B5B",
+ "[ c #5D5D5D",
+ "} c #5F5F5F",
+ "| c #616161",
+ "1 c #626262",
+ "2 c #636363",
+ "3 c #8B8B8B",
+ "4 c #DDDDDD",
+ "5 c #C7C7C8",
+ "6 c #A5A5A6",
+ "7 c #919193",
+ "8 c #7B7B7D",
+ "9 c #151522",
+ "0 c #444444",
+ "a c #4E4E4E",
+ "b c #555555",
+ "c c #5A5A5A",
+ "d c #5C5C5C",
+ "e c #5E5E5E",
+ "f c #646464",
+ "g c #676767",
+ "h c #999999",
+ "i c #E5E5E5",
+ "j c #DCDCDC",
+ "k c #D3D3D3",
+ "l c #CFCFCF",
+ "m c #CBCBCB",
+ "n c #C5C5C5",
+ "o c #969697",
+ "p c #707072",
+ "q c #000024",
+ "r c #26262C",
+ "s c #474747",
+ "t c #505050",
+ "u c #575757",
+ "v c #606060",
+ "w c #656565",
+ "x c #666666",
+ "y c #6D6D6D",
+ "z c #A7A7A7",
+ "A c #E7E7E7",
+ "B c #DADADA",
+ "C c #D4D4D4",
+ "D c #D1D1D1",
+ "E c #CCCCCC",
+ "F c #C6C6C6",
+ "G c #BFBFBF",
+ "H c #B7B7B7",
+ "I c #979798",
+ "J c #656568",
+ "K c #323235",
+ "L c #484848",
+ "M c #595959",
+ "N c #686868",
+ "O c #737373",
+ "P c #B6B6B6",
+ "Q c #E6E6E6",
+ "R c #D8D8D8",
+ "S c #D6D6D6",
+ "T c #D2D2D2",
+ "U c #CDCDCD",
+ "V c #C7C7C7",
+ "W c #C0C0C0",
+ "X c #AFAFAF",
+ "Y c #A5A5A5",
+ "Z c #868687",
+ "` c #33333A",
+ " . c #000025",
+ ".. c #39393B",
+ "+. c #4C4C4C",
+ "@. c #545454",
+ "#. c #696969",
+ "$. c #7C7C7C",
+ "%. c #C4C4C4",
+ "&. c #D9D9D9",
+ "*. c #D7D7D7",
+ "=. c #CECECE",
+ "-. c #C8C8C8",
+ ";. c #C1C1C1",
+ ">. c #B8B8B8",
+ ",. c #9A9A9A",
+ "'. c #8E8E8E",
+ "). c #424247",
+ "!. c #434343",
+ "~. c #4F4F4F",
+ "{. c #6A6A6A",
+ "]. c #6B6B6B",
+ "^. c #6C6C6C",
+ "/. c #878787",
+ "(. c #DBDBDB",
+ "_. c #C9C9C9",
+ ":. c #B0B0B0",
+ "<. c #8D8D8D",
+ "[. c #838383",
+ "}. c #3E3E43",
+ "|. c #000026",
+ "1. c #161624",
+ "2. c #464646",
+ "3. c #6E6E6E",
+ "4. c #DEDEDE",
+ "5. c #D5D5D5",
+ "6. c #D0D0D0",
+ "7. c #CACACA",
+ "8. c #B9B9B9",
+ "9. c #9B9B9B",
+ "0. c #69696A",
+ "a. c #27272D",
+ "b. c #4A4A4A",
+ "c. c #6F6F6F",
+ "d. c #707070",
+ "e. c #717171",
+ "f. c #E4E4E4",
+ "g. c #DFDFDF",
+ "h. c #C2C2C2",
+ "i. c #BABABA",
+ "j. c #8C8C8C",
+ "k. c #6F6F70",
+ "l. c #000027",
+ "m. c #727272",
+ "n. c #757575",
+ "o. c #AAAAAA",
+ "p. c #E0E0E0",
+ "q. c #B1B1B1",
+ "r. c #9C9C9C",
+ "s. c #828282",
+ "t. c #676768",
+ "u. c #3B3B3D",
+ "v. c #747474",
+ "w. c #E1E1E1",
+ "x. c #5E5E60",
+ "y. c #000028",
+ "z. c #3E3E40",
+ "A. c #535353",
+ "B. c #767676",
+ "C. c #E3E3E3",
+ "D. c #E2E2E2",
+ "E. c #8A8A8A",
+ "F. c #818181",
+ "G. c #5C5C5E",
+ "H. c #000029",
+ "I. c #161626",
+ "J. c #494949",
+ "K. c #565656",
+ "L. c #777777",
+ "M. c #787878",
+ "N. c #797979",
+ "O. c #989898",
+ "P. c #898989",
+ "Q. c #525255",
+ "R. c #181828",
+ "S. c #4D4D4D",
+ "T. c #7A7A7A",
+ "U. c #7B7B7B",
+ "V. c #979797",
+ "W. c #A4A4A4",
+ "X. c #47474C",
+ "Y. c #00002A",
+ "Z. c #292930",
+ "`. c #7D7D7D",
+ " + c #7E7E7E",
+ ".+ c #9E9E9E",
+ "++ c #AEAEAE",
+ "@+ c #A3A3A3",
+ "#+ c #969696",
+ "$+ c #7F7F7F",
+ "%+ c #393940",
+ "&+ c #38383B",
+ "*+ c #ACACAC",
+ "=+ c #ADADAD",
+ "-+ c #949494",
+ ";+ c #888888",
+ ">+ c #37373E",
+ ",+ c #00002B",
+ "'+ c #404042",
+ ")+ c #808080",
+ "!+ c #858585",
+ "~+ c #BBBBBB",
+ "{+ c #939393",
+ "]+ c #252532",
+ "^+ c #848484",
+ "/+ c #E8E8E8",
+ "(+ c #ABABAB",
+ "_+ c #A2A2A2",
+ ":+ c #919191",
+ "<+ c #868686",
+ "[+ c #00002C",
+ "}+ c #18182A",
+ "|+ c #E9E9E9",
+ "1+ c #A0A0A0",
+ "2+ c #8F8F8F",
+ "3+ c #727273",
+ "4+ c #2A2A32",
+ "5+ c #EBEBEB",
+ "6+ c #ECECEC",
+ "7+ c #B4B4B4",
+ "8+ c #A9A9A9",
+ "9+ c #00002D",
+ "0+ c #2F2F36",
+ "a+ c #A8A8A8",
+ "b+ c #EDEDED",
+ "c+ c #EFEFEF",
+ "d+ c #F0F0F0",
+ "e+ c #BEBEBE",
+ "f+ c #B3B3B3",
+ "g+ c #666668",
+ "h+ c #414143",
+ "i+ c #F4F4F4",
+ "j+ c #F5F5F5",
+ "k+ c #F1F1F1",
+ "l+ c #BDBDBD",
+ "m+ c #B2B2B2",
+ "n+ c #A6A6A6",
+ "o+ c #5D5D60",
+ "p+ c #00002E",
+ "q+ c #4A4A4B",
+ "r+ c #909090",
+ "s+ c #F9F9F9",
+ "t+ c #FCFCFC",
+ "u+ c #F6F6F6",
+ "v+ c #BCBCBC",
+ "w+ c #5A5A5D",
+ "x+ c #19192C",
+ "y+ c #929292",
+ "z+ c #EAEAEA",
+ "A+ c #F8F8F8",
+ "B+ c #FFFFFF",
+ "C+ c #4F4F53",
+ "D+ c #00002F",
+ "E+ c #232330",
+ "F+ c #959595",
+ "G+ c #F2F2F2",
+ "H+ c #FDFDFD",
+ "I+ c #44444A",
+ "J+ c #000030",
+ "K+ c #2F2F37",
+ "L+ c #EEEEEE",
+ "M+ c #9F9F9F",
+ "N+ c #373740",
+ "O+ c #3E3E42",
+ "P+ c #FAFAFA",
+ "Q+ c #9D9D9D",
+ "R+ c #34343E",
+ "S+ c #000031",
+ "T+ c #47474A",
+ "U+ c #232334",
+ "V+ c #A1A1A1",
+ "W+ c #000032",
+ "X+ c #1B1B2F",
+ "Y+ c #F3F3F3",
+ "Z+ c #303039",
+ "`+ c #666667",
+ " @ c #000033",
+ ".@ c #3A3A40",
+ "+@ c #B5B5B5",
+ "@@ c #5D5D5F",
+ "#@ c #48484B",
+ "$@ c #F7F7F7",
+ "%@ c #545458",
+ "&@ c #000034",
+ "*@ c #525254",
+ "=@ c #515155",
+ "-@ c #000035",
+ ";@ c #1B1B31",
+ ">@ c #46464C",
+ ",@ c #282837",
+ "'@ c #3B3B44",
+ ")@ c #000036",
+ "!@ c #35353F",
+ "~@ c #C3C3C3",
+ "{@ c #30303D",
+ "]@ c #45454A",
+ "^@ c #FBFBFB",
+ "/@ c #2C2C3A",
+ "(@ c #000037",
+ "_@ c #505053",
+ ":@ c #1D1D34",
+ "<@ c #04042B",
+ "[@ c #080822",
+ "}@ c #0B0B1F",
+ "|@ c #0F0F19",
+ "1@ c #121217",
+ "2@ c #11111A",
+ "3@ c #11111E",
+ "4@ c #12121F",
+ "5@ c #13131F",
+ "6@ c #0F0F25",
+ "7@ c #1C1C34",
+ "8@ c #02022C",
+ "9@ c #040422",
+ "0@ c #050522",
+ "a@ c #090916",
+ "b@ c #0C0C0C",
+ "c@ c #0F0F0F",
+ "d@ c #101010",
+ "e@ c #131313",
+ "f@ c #141414",
+ "g@ c #171717",
+ "h@ c #181818",
+ "i@ c #191919",
+ "j@ c #1A1A1A",
+ "k@ c #1C1C1C",
+ "l@ c #1D1D1D",
+ "m@ c #1C1C20",
+ "n@ c #191924",
+ "o@ c #0B0B2D",
+ "p@ c #000038",
+ "q@ c #1E1E36",
+ "r@ c #060623",
+ "s@ c #0A0A0A",
+ "t@ c #0B0B0B",
+ "u@ c #0E0E0E",
+ "v@ c #1F1F1F",
+ "w@ c #1E1E1E",
+ "x@ c #202020",
+ "y@ c #222222",
+ "z@ c #212121",
+ "A@ c #1B1B26",
+ "B@ c #151529",
+ "C@ c #353540",
+ "D@ c #555557",
+ "E@ c #06062D",
+ "F@ c #0B0B24",
+ "G@ c #0E0E19",
+ "H@ c #121212",
+ "I@ c #161616",
+ "J@ c #151515",
+ "K@ c #242424",
+ "L@ c #252525",
+ "M@ c #232323",
+ "N@ c #282828",
+ "O@ c #262626",
+ "P@ c #212127",
+ "Q@ c #000039",
+ "R@ c #404047",
+ "S@ c #515153",
+ "T@ c #08082E",
+ "U@ c #11111F",
+ "V@ c #1B1B1B",
+ "W@ c #2C2C2C",
+ "X@ c #272727",
+ "Y@ c #2E2E2E",
+ "Z@ c #2B2B2B",
+ "`@ c #2A2A2A",
+ " # c #292929",
+ ".# c #232329",
+ "+# c #14142D",
+ "@# c #00003A",
+ "## c #515154",
+ "$# c #50505A",
+ "%# c #111127",
+ "&# c #19191D",
+ "*# c #333333",
+ "=# c #363636",
+ "-# c #2D2D2D",
+ ";# c #323232",
+ "># c #2F2F2F",
+ ",# c #15152E",
+ "'# c #000000",
+ ")# c #4D4D58",
+ "!# c #171727",
+ "~# c #353535",
+ "{# c #393939",
+ "]# c #404040",
+ "^# c #3F3F3F",
+ "/# c #313131",
+ "(# c #303030",
+ "_# c #373737",
+ ":# c #383838",
+ "<# c #16162F",
+ "[# c #00003B",
+ "}# c #3C3C3C",
+ "|# c #3B3B3B",
+ "1# c #010101",
+ "2# c #020202",
+ "3# c #3F3F4C",
+ "4# c #1B1B2A",
+ "5# c #343434",
+ "6# c #3D3D3D",
+ "7# c #454545",
+ "8# c #515151",
+ "9# c #414141",
+ "0# c #3E3E3E",
+ "a# c #171730",
+ "b# c #030303",
+ "c# c #040404",
+ "d# c #050505",
+ "e# c #7A7A7B",
+ "f# c #1F1F2D",
+ "g# c #424242",
+ "h# c #3A3A3A",
+ "i# c #2D2D30",
+ "j# c #00003C",
+ "k# c #000015",
+ "l# c #080808",
+ "m# c #343443",
+ "n# c #1E1E30",
+ "o# c #181831",
+ "p# c #060606",
+ "q# c #111111",
+ "r# c #54545A",
+ "s# c #121233",
+ "t# c #303033",
+ "u# c #00003D",
+ "v# c #000016",
+ "w# c #666669",
+ "x# c #141435",
+ "y# c #202032",
+ "z# c #00003E",
+ "A# c #606032",
+ "B# c #6C6C6E",
+ "C# c #303038",
+ "D# c #2F2F35",
+ "E# c #090909",
+ "F# c #8E8E14",
+ "G# c #B9B915",
+ "H# c #CCCC1E",
+ "I# c #DBDB1C",
+ "J# c #E4E400",
+ "K# c #DEDE00",
+ "L# c #BDBD25",
+ "M# c #828228",
+ "N# c #676769",
+ "O# c #171737",
+ "P# c #00003F",
+ "Q# c #696953",
+ "R# c #C4C405",
+ "S# c #E7E700",
+ "T# c #E9E900",
+ "U# c #E8E800",
+ "V# c #E3E300",
+ "W# c #DBDB00",
+ "X# c #D0D000",
+ "Y# c #AAAA0C",
+ "Z# c #545409",
+ "`# c #57575B",
+ " $ c #292939",
+ ".$ c #1B1B34",
+ "+$ c #000017",
+ "@$ c #97972D",
+ "#$ c #E5E500",
+ "$$ c #E2E200",
+ "%$ c #B8B800",
+ "&$ c #676751",
+ "*$ c #3E3E45",
+ "=$ c #191938",
+ "-$ c #282835",
+ ";$ c #000040",
+ ">$ c #626259",
+ ",$ c #939300",
+ "'$ c #C0C000",
+ ")$ c #DDDD00",
+ "!$ c #E0E000",
+ "~$ c #D9D900",
+ "{$ c #CACA00",
+ "]$ c #A8A800",
+ "^$ c #5B5B5D",
+ "/$ c #0E0E35",
+ "($ c #2D2D3C",
+ "_$ c #292936",
+ ":$ c #5F5F14",
+ "<$ c #848400",
+ "[$ c #AAAA00",
+ "}$ c #E6E600",
+ "|$ c #D6D600",
+ "1$ c #8F8F00",
+ "2$ c #1B1B24",
+ "3$ c #2F2F3E",
+ "4$ c #2A2A37",
+ "5$ c #000041",
+ "6$ c #4E4E00",
+ "7$ c #707000",
+ "8$ c #858500",
+ "9$ c #A7A700",
+ "0$ c #BEBE00",
+ "a$ c #D5D500",
+ "b$ c #C3C300",
+ "c$ c #6D6D00",
+ "d$ c #2B2B38",
+ "e$ c #000042",
+ "f$ c #1A1A00",
+ "g$ c #4D4D00",
+ "h$ c #6C6C00",
+ "i$ c #838300",
+ "j$ c #A4A400",
+ "k$ c #BCBC00",
+ "l$ c #CFCF00",
+ "m$ c #E1E100",
+ "n$ c #D2D200",
+ "o$ c #BFBF00",
+ "p$ c #000011",
+ "q$ c #1D1D3D",
+ "r$ c #171700",
+ "s$ c #474700",
+ "t$ c #696900",
+ "u$ c #818100",
+ "v$ c #A2A200",
+ "w$ c #BBBB00",
+ "x$ c #CECE00",
+ "y$ c #DADA00",
+ "z$ c #AFAF00",
+ "A$ c #1E1E3D",
+ "B$ c #252530",
+ "C$ c #000043",
+ "D$ c #121200",
+ "E$ c #414100",
+ "F$ c #676700",
+ "G$ c #808000",
+ "H$ c #A0A000",
+ "I$ c #DFDF00",
+ "J$ c #D8D800",
+ "K$ c #8D8D00",
+ "L$ c #1E1E3E",
+ "M$ c #151525",
+ "N$ c #0A0A00",
+ "O$ c #373700",
+ "P$ c #656500",
+ "Q$ c #7F7F00",
+ "R$ c #9F9F00",
+ "S$ c #C2C200",
+ "T$ c #525230",
+ "U$ c #2C2C41",
+ "V$ c #111124",
+ "W$ c #000044",
+ "X$ c #000019",
+ "Y$ c #343400",
+ "Z$ c #616100",
+ "`$ c #7E7E00",
+ " % c #DCDC00",
+ ".% c #D1D100",
+ "+% c #AEAE00",
+ "@% c #2E2E43",
+ "#% c #090921",
+ "$% c #2E2E00",
+ "%% c #5D5D00",
+ "&% c #7D7D00",
+ "*% c #D3D300",
+ "=% c #7F7F1F",
+ "-% c #01011F",
+ ";% c #000045",
+ ">% c #282800",
+ ",% c #5A5A00",
+ "'% c #7C7C00",
+ ")% c #D4D400",
+ "!% c #4B4B30",
+ "~% c #000046",
+ "{% c #232300",
+ "]% c #545400",
+ "^% c #7A7A00",
+ "/% c #C1C100",
+ "(% c #A4A412",
+ "_% c #181800",
+ ":% c #505000",
+ "<% c #787800",
+ "[% c #C3C301",
+ "}% c #6F6F26",
+ "|% c #515158",
+ "1% c #000047",
+ "2% c #131300",
+ "3% c #777700",
+ "4% c #A1A100",
+ "5% c #C4C401",
+ "6% c #D7D700",
+ "7% c #BDBD00",
+ "8% c #414132",
+ "9% c #242444",
+ "0% c #141400",
+ "a% c #4C4C00",
+ "b% c #767600",
+ "c% c #A0A001",
+ "d% c #C6C602",
+ "e% c #94941A",
+ "f% c #5D5D61",
+ "g% c #000012",
+ "h% c #000048",
+ "i% c #747400",
+ "j% c #C8C803",
+ "k% c #DBDB01",
+ "l% c #C4C400",
+ "m% c #4F4F33",
+ "n% c #424251",
+ "o% c #000049",
+ "p% c #0B0B27",
+ "q% c #42421A",
+ "r% c #717100",
+ "s% c #9D9D02",
+ "t% c #C9C907",
+ "u% c #DCDC03",
+ "v% c #616165",
+ "w% c #000013",
+ "x% c #37371B",
+ "y% c #6A6A00",
+ "z% c #999904",
+ "A% c #CDCD0C",
+ "B% c #DDDD05",
+ "C% c #E4E401",
+ "D% c #DCDC01",
+ "E% c #5A5A2E",
+ "F% c #6F6F71",
+ "G% c #00004A",
+ "H% c #2C2C1B",
+ "I% c #646400",
+ "J% c #979707",
+ "K% c #D3D313",
+ "L% c #DEDE07",
+ "M% c #E3E303",
+ "N% c #D5D501",
+ "O% c #3A3A4E",
+ "P% c #23231B",
+ "Q% c #5E5E00",
+ "R% c #98980A",
+ "S% c #DADA1C",
+ "T% c #DEDE06",
+ "U% c #E4E403",
+ "V% c #E1E106",
+ "W% c #C5C501",
+ "X% c #5D5D2E",
+ "Y% c #3B3B50",
+ "Z% c #00004B",
+ "`% c #1C1C2F",
+ " & c #585800",
+ ".& c #9A9A0E",
+ "+& c #E3E326",
+ "@& c #DDDD06",
+ "#& c #E6E608",
+ "$& c #DBDB08",
+ "%& c #3A3A4F",
+ "&& c #171735",
+ "*& c #525200",
+ "=& c #A2A218",
+ "-& c #EBEB2E",
+ ";& c #EBEB12",
+ ">& c #CCCC08",
+ ",& c #636329",
+ "'& c #3C3C51",
+ ")& c #777779",
+ "!& c #00004C",
+ "~& c #121236",
+ "{& c #4B4B00",
+ "]& c #A5A521",
+ "^& c #ECEC30",
+ "/& c #E2E20C",
+ "(& c #ECEC1A",
+ "_& c #A9A903",
+ ":& c #3E3E48",
+ "<& c #00004D",
+ "[& c #000014",
+ "}& c #080837",
+ "|& c #3E3E2A",
+ "1& c #91912C",
+ "2& c #A6A623",
+ "3& c #B5B532",
+ "4& c #888825",
+ "5& c #2C2C3E",
+ "6& c #070707",
+ "7& c #00004E",
+ "8& c #0D0D0D",
+ "9& c #00004F",
+ "0& c #32324C",
+ "a& c #5F5F64",
+ "b& c #565659",
+ "c& c #050539",
+ "d& c #000050",
+ "e& c #1D1D47",
+ "f& c #353541",
+ "g& c #3E3E50",
+ "h& c #18183E",
+ "i& c #000051",
+ "j& c #56565C",
+ "k& c #3F3F43",
+ "l& c #000052",
+ "m& c #1D1D42",
+ "n& c #272737",
+ "o& c #01013A",
+ "p& c #10103D",
+ "q& c #000053",
+ "r& c #090944",
+ "s& c #363643",
+ "t& c #000054",
+ "u& c #03031F",
+ "v& c #151539",
+ "w& c #171740",
+ "x& c #242430",
+ "y& c #000055",
+ "z& c #040416",
+ "A& c #0B0B46",
+ "B& c #2A2A3A",
+ "C& c #444453",
+ "D& c #0F0F3F",
+ "E& c #000056",
+ "F& c #FEFEFE",
+ "G& c #22224E",
+ "H& c #090946",
+ "I& c #35353A",
+ "J& c #000057",
+ "K& c #747479",
+ "L& c #14143A",
+ "M& c #1A1A44",
+ "N& c #47475A",
+ "O& c #36363F",
+ "P& c #39393E",
+ "Q& c #000058",
+ "R& c #1F1F4E",
+ "S& c #21214F",
+ "T& c #29293A",
+ "U& c #000059",
+ "V& c #51515F",
+ "W& c #46464E",
+ "X& c #131342",
+ "Y& c #21213B",
+ "Z& c #1D1D4E",
+ "`& c #00005A",
+ " * c #45455A",
+ ".* c #212135",
+ "+* c #00005B",
+ "@* c #76767B",
+ "#* c #4C4C50",
+ "$* c #2C2C56",
+ "%* c #28283A",
+ "&* c #00005C",
+ "** c #5B5B62",
+ "=* c #161645",
+ "-* c #242453",
+ ";* c #00005D",
+ ">* c #000018",
+ ",* c #48485D",
+ "'* c #222237",
+ ")* c #00005E",
+ "!* c #737379",
+ "~* c #0A0A4C",
+ "{* c #2C2C33",
+ "]* c #303041",
+ "^* c #00005F",
+ "/* c #151547",
+ "(* c #39393F",
+ "_* c #000060",
+ ":* c #232339",
+ "<* c #000061",
+ "[* c #0E0E47",
+ "}* c #000062",
+ "|* c #000063",
+ "1* c #000064",
+ "2* c #000065",
+ "3* c #000066",
+ "4* c #000067",
+ "5* c #000068",
+ "6* c #000069",
+ "7* c #00006A",
+ "8* c #00006B",
+ "9* c #00006C",
+ "0* c #00006D",
+ "a* c #00006E",
+ "b* c #00006F",
+ "c* c #0D0D2B",
+ "d* c #2E2E3E",
+ "e* c #000070",
+ "f* c #131352",
+ "g* c #282832",
+ "h* c #000071",
+ "i* c #000072",
+ "j* c #000073",
+ "k* c #3D3D00",
+ "l* c #3F3F35",
+ "m* c #000074",
+ "n* c #404000",
+ "o* c #5B5B00",
+ "p* c #686800",
+ "q* c #666600",
+ "r* c #515135",
+ "s* c #48483F",
+ "t* c #1D1D5E",
+ "u* c #46462B",
+ "v* c #424200",
+ "w* c #3E3E00",
+ "x* c #595900",
+ "y* c #6B6B00",
+ "z* c #5D5D2B",
+ "A* c #4C4C3F",
+ "B* c #000075",
+ "C* c #404049",
+ "D* c #636300",
+ "E* c #626200",
+ "F* c #363600",
+ "G* c #0F0F00",
+ "H* c #272700",
+ "I* c #303000",
+ "J* c #5F5F00",
+ "K* c #6E6E00",
+ "L* c #6F6F00",
+ "M* c #373753",
+ "N* c #000076",
+ "O* c #515140",
+ "P* c #8E8E00",
+ "Q* c #878700",
+ "R* c #757500",
+ "S* c #727200",
+ "T* c #4B4B2B",
+ "U* c #0D0D00",
+ "V* c #2C2C00",
+ "W* c #464600",
+ "X* c #535300",
+ "Y* c #737300",
+ "Z* c #3B3B53",
+ "`* c #1F1F5F",
+ " = c #555536",
+ ".= c #898900",
+ "+= c #999900",
+ "@= c #A5A500",
+ "#= c #9B9B00",
+ "$= c #8B8B00",
+ "%= c #888800",
+ "&= c #20205F",
+ "*= c #101000",
+ "== c #323200",
+ "-= c #494900",
+ ";= c #797900",
+ ">= c #7B7B00",
+ ",= c #7C7C01",
+ "'= c #333354",
+ ")= c #000077",
+ "!= c #101060",
+ "~= c #434337",
+ "{= c #828200",
+ "]= c #929200",
+ "^= c #ACAC00",
+ "/= c #B0B000",
+ "(= c #9D9D00",
+ "_= c #9A9A00",
+ ":= c #5E5E40",
+ "<= c #010140",
+ "[= c #111100",
+ "}= c #7E7E02",
+ "|= c #808004",
+ "1= c #82820C",
+ "2= c #888811",
+ "3= c #7C7C0C",
+ "4= c #2D2D54",
+ "5= c #1F1F40",
+ "6= c #484800",
+ "7= c #868600",
+ "8= c #9E9E00",
+ "9= c #B4B400",
+ "0= c #B5B500",
+ "a= c #B6B600",
+ "b= c #B2B200",
+ "c= c #959500",
+ "d= c #282860",
+ "e= c #06064A",
+ "f= c #0E0E00",
+ "g= c #1C1C00",
+ "h= c #383800",
+ "i= c #818113",
+ "j= c #868614",
+ "k= c #808002",
+ "l= c #828201",
+ "m= c #7E7E04",
+ "n= c #6F6F05",
+ "o= c #565601",
+ "p= c #4B4B01",
+ "q= c #37371E",
+ "r= c #161637",
+ "s= c #000078",
+ "t= c #1E1E1F",
+ "u= c #3F3F00",
+ "v= c #606000",
+ "w= c #989800",
+ "x= c #A9A900",
+ "y= c #B3B300",
+ "z= c #545441",
+ "A= c #070737",
+ "B= c #0B0B00",
+ "C= c #7D7D26",
+ "D= c #8E8E2D",
+ "E= c #727203",
+ "F= c #34341F",
+ "G= c #02024B",
+ "H= c #10101F",
+ "I= c #353500",
+ "J= c #4F4F00",
+ "K= c #909000",
+ "L= c #BABA00",
+ "M= c #494941",
+ "N= c #060641",
+ "O= c #2B2B00",
+ "P= c #000079",
+ "Q= c #202000",
+ "R= c #575700",
+ "S= c #717107",
+ "T= c #858519",
+ "U= c #8C8C20",
+ "V= c #6D6D04",
+ "W= c #717104",
+ "X= c #737304",
+ "Y= c #787804",
+ "Z= c #84840B",
+ "`= c #96960E",
+ " - c #A0A00F",
+ ".- c #A0A004",
+ "+- c #939303",
+ "@- c #60601F",
+ "#- c #111156",
+ "$- c #1D1D00",
+ "%- c #2D2D00",
+ "&- c #515106",
+ "*- c #525207",
+ "=- c #6B6B1C",
+ "-- c #5F5F15",
+ ";- c #434302",
+ ">- c #404001",
+ ",- c #444402",
+ "'- c #4E4E04",
+ ")- c #686810",
+ "!- c #6C6C11",
+ "~- c #555500",
+ "{- c #39391F",
+ "]- c #09094C",
+ "^- c #00007A",
+ "/- c #09092D",
+ "(- c #0C0C38",
+ "_- c #050538",
+ ":- c #070738",
+ "<- c #00007B",
+ "[- c #00007C",
+ "}- c #00007D",
+ ". . . . . . . . . . . + + + + + + + + + + + + + + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . ",
+ "% % % . . . . . . . . . . . . . . . . + + + + + + + + + + + + + + + + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + + + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . . . . % % % % % ",
+ "% % % % % % % % % % % . . . . . . . . . . . . . . . . . + + + + + + + + + + + + + + + + + + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + + + + + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . . . . . % % % % % % % % % % % % % ",
+ "& & & % % % % % % % % % % % % % % % % % . . . . . . . . . . . . . . . . . . . + + + + + + + + + + + + + + + + + + + + + + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + + + + + + + + + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . . . . . . . % % % % % % % % % % % % % % % % % & & & & & ",
+ "& & & & & & & & & & & % % % % % % % % % % % % % % % % % % . . . . . . . . . . . . . . . . . . . . . . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . . . . . . . . . . % % % % % % % % % % % % % % % % % % & & & & & & & & & & & & & ",
+ "* * * & & & & & & & & & & & & & & & & & % % % % % % % % % % % % % % % % % % % % . . . . . . . . . . . . . . . . . . . . . . . . . . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . . . . . . . . . . . . . . % % % % % % % % % % % % % % % % % % % % & & & & & & & & & & & & & & & & & * * * * * ",
+ "* * * * * * * * * * * * & & & & & & & & & & & & & & & & & & % % % % % % % % % % % % % % % % % % % % % % % . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . % % % % % % % % % % % % % % % % % % % % % % % & & & & & & & & & & & & & & & & & & * * * * * * * * * * * * * * ",
+ "= = = = * * * * * * * * * * * * * * * * * & & & & & & & & & & & & & & & & & & & & & % % % % % % % % % % % % % % % % % % % % % % % % % % % . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . % % % % % % % % % % % % % % % % % % % % % % % % % % % & & & & & & & & & & & & & & & & & & & & & * * * * * * * * * * * * * * * * * = = = = = = ",
+ "= = = = = = = = = = = = = * * * * * * * * * * * * * * * * * * * & & & & & & & & & & & & & & & & & & & & & & & % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % . . . . - ; > , ' ) ) ! , ~ { { ] % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % & & & & & & & & & & & & & & & & & & & & & & & * * * * * * * * * * * * * * * * * * * = = = = = = = = = = = = = = = ",
+ "^ ^ ^ ^ = = = = = = = = = = = = = = = = = = * * * * * * * * * * * * * * * * * * * * * & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % / ( _ : < [ [ [ < < } | 1 2 3 4 5 6 7 8 % % % % % % % % % % % % % % % % % % % % % % & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & * * * * * * * * * * * * * * * * * * * * * = = = = = = = = = = = = = = = = = = ^ ^ ^ ^ ^ ^ ",
+ "^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ = = = = = = = = = = = = = = = = = = = * * * * * * * * * * * * * * * * * * * * * * * * * & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & 9 0 a b c d e e e d [ | 2 f g h i j k l m n o p & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & * * * * * * * * * * * * * * * * * * * * * * * * * = = = = = = = = = = = = = = = = = = = ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ",
+ "q q q q q ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ = = = = = = = = = = = = = = = = = = = = = * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * & & & & & & & & & & & & & & & & & & & & & & & & & & r s t u < e } } e d v f w x y z A B C D E F G H I J & & & & & & & & & & & & & * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * = = = = = = = = = = = = = = = = = = = = = ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ q q q q q q q ",
+ "q q q q q q q q q q q q q q q ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ = = = = = = = = = = = = = = = = = = = = = = = = = = * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * K L _ M [ v v v } e 1 x g N O P Q R S T U V W H X Y Z ` * * * * * * * * * * * * * * * * * * * * * * * * * * * = = = = = = = = = = = = = = = = = = = = = = = = = = ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ q q q q q q q q q q q q q q q q q ",
+ " . . . . . .q q q q q q q q q q q q q q q q q q q ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * * * * * * * * * * * * * * * * * * * * ..+.@.< e | | | v | w N #.#.$.%.i &.*.k =.-.;.>.X Y ,.'.).* * * * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ q q q q q q q q q q q q q q q q q q q . . . . . . . .",
+ " . . . . . . . . . . . . . . . .q q q q q q q q q q q q q q q q q q q q q ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = !.~.u [ v 1 2 1 | f N {.].^./.T 4 (.R C l _.;.>.:.Y ,.<.[.}.= = = = = = = = = = = = = = = = = = = = = = ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ q q q q q q q q q q q q q q q q q q q q q . . . . . . . . . . . . . . . . . .",
+ "|.|.|.|.|.|.|. . . . . . . . . . . . . . . . . . . . .q q q q q q q q q q q q q q q q q q q q q q q q ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ = = = = = = = = = = = 1.2._ M } 1 f f 2 1 x {.y y 3.<.4 4.j &.5.6.7.;.8.:.Y 9.<.[.0.^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ q q q q q q q q q q q q q q q q q q q q q q q q . . . . . . . . . . . . . . . . . . . .|.|.|.|.|.|.|.|.|.",
+ "|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|. . . . . . . . . . . . . . . . . . . . . . .q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ a.b.@.d v 2 w w f w #.y c.d.e.9.f.g.4 B S D 7.h.i.:.Y ,.j.[.k.^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q . . . . . . . . . . . . . . . . . . . . . .|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+ "l.l.l.l.l.l.l.l.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|. . . . . . . . . . . . . . . . . . . . . . . . . .q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q ; +.u e 1 w x x w g ^.d.e.m.n.o.A p.4.(.*.D 7.h.i.q.Y r.j.s.t.q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q . . . . . . . . . . . . . . . . . . . . . . . . .|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.l.l.l.l.l.l.l.l.l.l.",
+ "l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .q q q q q q q q q q q q q q q q q q q q q q q q q q q q u.t M v f x g g g {.3.m.O v.$.8.A w.g.j *.T m h.i.q.Y 9.3 s.x.q q q q q q q q q q q q . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.y.",
+ "y.y.y.y.y.y.y.y.y.y.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .z.A.d 1 w N N N #.y e.n.B.B.s.V C.D.p.j R T 7.h.i.:.Y ,.E.F.G. . . . . . . . . . . . . . . . . . . . . . . . . . . . .|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.y.y.y.y.y.y.y.y.y.y.y.y.",
+ "H.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|. . . . . . . . . . . . . . . . . . . . .I.J.K.e f g #.#.#.^.d.v.L.M.N.3 C f.C.p.4 R T 7.h.i.:.Y O.P.F.Q. . . . . .|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.H.H.H.",
+ "H.H.H.H.H.H.H.H.H.H.H.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.R.S.M v x N {.{.{.3.O L.T.T.U.V.4.i C.w.4 &.T 7.h.8.X W.O.3 F.X.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.H.H.H.H.H.H.H.H.H.H.H.H.H.",
+ "Y.Y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.|.|.|.|.|.|.|.|.|.Z.t d 2 g {.].].^.e.B.T.$.`. +.+f.i f.D.4.&.k 7.;.8.++@+#+P.$+%+l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.Y.Y.Y.Y.",
+ "Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.&+A.e w #.].y ^.c.v.N.`. +$+F.*+A Q i C.g.B k 7.;.8.=+@+-+;+`.>+l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.",
+ ",+,+,+,+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.'+u v g {.y 3.y O L.$.)+F.s.!+~+A A Q f.w.(.k 7.W >.*+@+{+/.$.]+y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.,+,+,+,+,+,+",
+ ",+,+,+,+,+,+,+,+,+,+,+,+,+,+,+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.( c 2 #.^.3.c.c.B.T.$+s.[.^+3 7.A /+A Q C.j k _.W H (+_+:+<+T.y.y.y.y.y.y.y.y.y.y.y.y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+",
+ "[+[+[+[+[+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.}+t [ w {.3.c.d.m.N.`.s.!+<+/.{+S /+|+|+/+i 4.k _.W P o.1+2+!+3+H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+[+[+[+[+[+[+[+",
+ "[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.4+@.v g ^.c.e.e.n.$.F.!+;+;+P.r.4.|+5+6+6+/+p.C -.G 7+8+.+<.[.k.H.H.H.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+",
+ "9+9+9+9+9+9+9+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.0+u 2 #.3.d.m.m.N.$+^+;+E.3 j.a+A |+b+c+d+6+D.C -.e+f+z 9.3 s.g+Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+9+9+9+9+9+9+9+9+9+",
+ "9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+h+c w ].c.m.O n.`.s./.3 <.'.'.7+/+5+d+i+j+k+i 5.V l+m+n+h j.F.o+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+",
+ "p+p+p+p+p+p+p+p+p+p+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+q+e g y e.O v.N.F.<+3 2+r+r+:+e+|+b+i+s+t+u+A S V v+X Y V.P.)+w+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+,+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+p+p+p+p+p+p+p+p+p+p+p+p+",
+ "p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+x+_ | #.c.m.v.n.$.^+P.'.y+y+{+#+E z+c+A+B+B+t+z+*.F i.=+W.-+<+ +C+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+D+",
+ "D+D+D+D+D+D+D+D+D+D+D+D+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+E+u f ^.e.v.B.M.)+;+<.y+F+F+F+r.R 5+G+H+B+B+B+6+*.n >.(+_+y+^+$.I+[+[+[+[+[+[+[+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+D+D+D+D+D+D+D+D+D+D+D+D+D+D+",
+ "J+J+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+K+< g y m.n.L.U.[.3 :+#+O.O.O.W.Q 6+u+B+B+B+B+L+S %.P 8+M+2+[.T.N+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+9+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+J+J+J+J+",
+ "J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+O+e #.d.v.B.M.$+;+2+F+h 9.9.9.=+A L+P+B+B+B+B+c+C h.7+z Q+j.F.M.R+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+",
+ "S+S+S+S+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+T+1 ].e.n.M.T.s.j.{+h .+M+.+Q+8./+c+H+B+B+B+B+d+k ;.m+Y h P.$+n.U+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+p+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+S+S+S+S+S+S+",
+ "S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+b w y O L.N. +<+r+V.Q+_+V+V+1+n /+k+B+B+B+B+B+c+D G X @+#+;+ +m.D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+",
+ "W+W+W+W+W+W+W+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+D+X+c N c.n.M.U.s.E.-+r._+n+Y W.@+=.|+Y+B+B+B+B+B+b+=.v+=+V+-+^+$.0.D+D+D+D+D+D+D+D+D+D+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+W+W+W+W+W+W+W+W+W+",
+ "W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+Z+e {.e.B.N.`.!+'.h V+z o.a+z z f.|+j+B+B+B+B+B+|+m >.o..+:+F.T.`+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+J+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+",
+ " @ @ @ @ @ @ @ @ @ @W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+.@1 y O M.T.)+P.{+Q+Y *+++*+o.++f.|+u+B+B+B+B+B+i V +@z r.<.$+M.@@S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+ @ @ @ @ @ @ @ @ @ @ @ @",
+ " @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+#@w c.n.N.U.^+<.O._+(+q.m+X *++@f.|+$@B+B+B+B+B+g.%.m+@+h E.`.n.%@S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+S+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @",
+ "&@&@&@&@&@&@&@&@&@&@&@&@&@&@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+*@N e.L.T.`./.:+Q+z :.P P f+X e+C./+$@B+B+B+B+s+R G ++1+F+<+T.O =@W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@",
+ "-@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+W+;@d ].O M.U.F.3 F+_+=+P v+i.P m+k D.A j+B+B+B+B+d+T ~+o.Q+:+s.M.d.>@W+W+W+W+W+W+W+W+W+ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@-@-@-@",
+ "-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @,@v y v.T.$.!+2+,.z f+l+;.e+8.7+B p.i G+B+B+B+B+A 7.P z ,.2+$+B.3.'@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@",
+ ")@)@)@)@)@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@!@f d.B.U. +P.{+.+=+8.~@F h.v+H p.4 D.c+B+B+B+u+4 ~@q.@+V.3 U.v.].{@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@)@)@)@)@)@)@)@",
+ ")@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@]@N m.M.$.s.<.V.@+f+W _.m F G ~+(.(.g.5+A+B+^@z+C v+*+M+{+/.M.m.N /@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@&@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@",
+ "(@(@(@(@(@(@(@(@(@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@_@].v.T.`.!+:+9.8+8.V 6.6.7.h.;.B *.(.i d+j+c+p.m H a+9.r+[.n.c.w :@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@-@<@[@[@}@|@1@2@3@4@5@6@-@-@-@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@(@(@(@(@(@(@(@(@(@(@(@",
+ "(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@-@-@-@-@-@-@-@-@-@-@-@-@-@-@7@e 3.n.U. +E.F+M+X W =.S 5.=.n _.*.C *.g./+z+C.5.h.q.W.O.j.$+O y | -@-@-@-@-@-@-@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@8@9@0@a@b@c@d@e@f@g@h@i@j@k@k@l@m@n@o@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@",
+ "p@p@p@p@p@p@p@p@p@p@p@p@p@p@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@q@2 d.L.$.s.'.O.W.P F 5.j &.D V 6.k 6.k &.g.p.R m ~+(+M+-+;+U.d.{.[ )@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@)@r@a@s@t@u@d@e@f@h@h@j@k@v@w@x@x@y@z@y@y@A@B@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@",
+ "p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@C@g m.N. +<+y+r.8+v+U j D.4.C _.l =.E =.k *.S =.h.7+n+9.r+!+L.3.g D@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@E@F@G@d@d@e@H@I@J@I@h@k@v@w@x@K@L@M@K@N@O@O@O@O@O@P@(@(@(@(@(@(@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@Q@",
+ "Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@R@#.n.T.)+E.F+1+++h.C C.w.D.*.m l 7.-.7.U l E n i.++V+V.j.F.O #.w S@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@T@U@g@g@j@j@j@V@l@L@k@k@x@N@y@M@N@W@X@N@Y@Z@`@`@W@`@ #.#+#p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@",
+ "@#@#@#@#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@##^.B.$.s.'.h W.7+_.*.h.~+8.X f+_+Q+r.r..+.+r.Y r.y+-+{+;+`.r+8.8._+$#p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@p@%#&#k@w@x@z@`@K@K@L@*#W@L@O@=#W@X@W@*#-#W@*#;#Y@>#;#-#-#W@Z@,#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@@#@#@#@#@#@#",
+ "@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@p@p@p@p@} d.M. +<+:+y+Q+y+h (+v+;.F ;.++a '#'#'#'#'#'#'#'#'#'#'#>#d.v+_.6.~@(+)#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@!#z@y@X@O@N@ #;#~#-#-#{#]#;#>#^#]#>#/#]#~#(#_#{#;#*#:#;#/#;#>#-#<#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#",
+ "[#[#[#[#[#[#[#[#[#[#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@d ]#}#W@'#'#'#'#'#v i.l+_.S *.n @+|#'#'#'#1#1#2#1#1#'#'#'#r.h.(./+4 G .+3#Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@4#L@ #`@>#5#(#;#:#2.6#=#^#t 7#{#s 8#9#|#S.0 ~#|#]#_#:#0#=#~#:#;#/#Y@a#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#[#[#[#[#[#[#[#[#[#[#[#[#",
+ "[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#q '#'#'#'#'#'#'#'#E.e+G l D.z+j >.M.'#1#2#b#c#d#d#c#b#1#*#i.V A s+d+k *+e#@#@#@#@#@#@#@#@#@#@#@#@#@#f#`@-#>#;#5#g#^#h#6#S._ 0 2.: M ( 8#} A.+.c @.!.( a 6#6#!.|#h#6#=#~#*#i#@#@#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#",
+ "j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#@#D+k#'#'#'#'#'#'#'#'#1+h.G k z+u+L+E 9.`@b#d#l#s@b@b@s@l#c#9#F V 6+B+^@p.>.{+m#@#@#@#@#@#@#@#@#@#@#@#n#>#(#_#:#{#}#7#_ J.0 8#| M _ v {.} | c.x } ].f K.e < s 2.+.9#0#!.h#h#_#5#o#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#",
+ "j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[#[# .'#'#'#'#'#'#'#'#2#=+-.l+k b+H+P+(.z b p#s@c@J@h@j@h@q#t@}#D %.|+c+_.*.W h r#[#[#[#[#[#[#[#[#[#[#s#*#~#:#}#J.0 !.L M e A.K.N y x ^.N.v.m.`.M.m.U.O {.e.g : [ : 7#7#s ^#^#}#_#t#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#",
+ "u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#v#'#'#'#'#'#'#'#2#p#9.T i.q.v.9.6+D.*+g s@q#j@M@Z@Y@`@z@g@b@7.l+6.< e@y@W.r.w#j#j#j#j#j#j#j#j#j#x#{ :#|#^#g#+.u t S.< ^.N 2 d.`.T.T.<+/.[.'.<.;+2+<+)+[.n.y d.| t A.S.!.7#]#|#_#y#j#j#j#j#j#j#j#j#j#j#j#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#",
+ "z#z#z#z#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#j#S+'#'#'#'#'#'#'#'#c#u@x (.8+J@L@Y@: C.++{.u@i@N@A#2.S.J.}#`@h@r.+@M+d@V@f@)+h B#j#j#j#j#j#j#j#j#j#C#}#^#2.2.J.a e w d e c.U.N.U.;+<.P.#+Q+,.z (+a+*+W.V+9.;+<+$+].g x A.L b.!.^#|#D#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#z#z#z#z#z#z#",
+ "z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#u#|.'#'#'#'#'#'#'#1#d#c@*#i.,.d#f@E#1#_.z f d@F#G#H#I#J#K#L#M#v@c@q.=+'#'#'#,.{+N#u#u#u#u#u#u#u#u#O#]#g#0 S.K._ A.[ d.e.y n.!+3 E.y+9.1+(++@e+V E l l m F v+o.V+2+`.U.3.: A._ s 0 0#:#u#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#",
+ "P#P#P#P#P#P#P#P#P#P#P#P#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#u#u#u#u#u#u#@ '#'#'#'#'#'#'#'#2#l#J@$+8+_ '#'#L -.O.Q#R#J#S#T#U#S#V#W#X#Y#Z#$+Y V+3 ^+a+E.`#z#z#z#z#z#z#z#z# $!.2.+.~.d w } v d.$+^+s.E.#+r.1+++i.V T B g.C.Q A Q C.4 6.W z ,.E.v.].#.c b.J.g#|#.$z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#P#P#P#P#P#P#P#P#P#P#P#P#P#P#",
+ "P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#z#+$'#'#'#'#'#'#'#'#'#1#c# #/.,.v.)+f+o.$+@$X#K#J#S#U#U##$$$W#X#%$&$2+V+X o.#+T.*$z#z#z#z#z#z#z#=$2.L t 8#b d y O 3.O F.:+#+V.V+(+H n 6.R 4 w.i |+5+6+6+5+|+i (.-.f+O.!+ +d.M ~.a 0 ^#-$P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#",
+ ";$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#+ '#'#'#'#'#'#'#'#'#'#'#'#0#$.r+9.r./.>$,$'$X#)$J#S#U#S#J#!$~${$]$M ^+2+<. +^$/$P#P#P#P#P#P#P#($b.S.K.} [ } {.$.[.^+P.#+_+8+X l+7.T *.j w.i |+6+L+c+c+L+6+|+f.j 7.++9.;+e.N f b L g#_$P#P#P#P#P#P#P#P#P#P#P#P#;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$",
+ ";$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#P#= '#'#'#'#'#'#'#'#'#'#'#'#'#`@< d.^.t :$<$[$'$X#)$J#S#S#}$V#K#|${$1$}#v f ~.2$P#P#P#P#P#P#P#P#3$S.A.u 2 c.^.].U.E.,.V+@+*++@G 7.6.5.B 4.C.A 5+L+d+k+k+d+b+z+Q p.&.n o.'.s.N.].A.( 7#4$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$",
+ "5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$;$p+'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#H@c@'#6$7$8$9$0$X#)$J#S#S##$$$)$a$b$c$1#c#'#p+;$;$;$;$;$;$;$;$]@A.K.[ 1 e. +`.)+j.1+q.H i.;.7.=.T *.(.p.i |+6+c+k+G+k+d+b+z+i p.B D >.M+'.`.g c _ 2.d$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$",
+ "e$e$e$e$e$e$e$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#f$g$h$i$j$k$l$)$J#}$}$J#m$W#n$o$6$'#p$5$5$5$5$5$5$5$5$q$_ K.v | w 3. +2+,.V+=+~+;.~@7.U 6.C R j w.i |+6+L+d+k+d+c+6+/+f.g.&.k %.o.y+$+B.y [ L Z+5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$e$e$e$e$e$e$e$e$e$",
+ "e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$5$p$'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#r$s$t$u$v$w$x$)$J#}$}$J#!$y$X#z$'#. 5$5$5$5$5$5$5$5$A$K.M e {.3.e.T.j.n+W E U _.7.E =.D C R j p.f./+5+b+L+L+L+6+|+Q w.4 *.D _.m+,.j.)+3.M ( B$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$",
+ "C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$q '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#D$E$F$G$H$w$l$K##$S#}$V#I$J${$K$D+e$e$e$e$e$e$e$e$L$: d } f e.U.[.r+W.%.R 5.=.U =.6.T C R (.g.D.Q /+z+5+5+z+|+Q C.g.B C l _.8.V+'.$.{.e a M$e$e$e$e$e$e$e$e$e$e$e$e$e$e$e$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$",
+ "C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$e$e$'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#N$O$P$Q$R$w$X#I$}$S#}$V#K#a$S$T$C$C$C$C$C$C$C$C$U$< | 1 f #.B.3 o.E p.A B 6.l l 6.T C *.B 4 p.C.i A A A Q f.D.g.(.S D E V i._+r+!+T.y J.V$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$",
+ "W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$C$X$'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#Y$Z$`$R$k$n$!$}$S#}$$$ %.%+%C$C$C$C$C$C$C$C$@%e 1 #.#.g y T.1+&.Y+L+4 D 6.l 6.D k 5.R B 4 g.w.D.C.C.D.p.4 B S T =._.%.i.n+#+E.U.{.2.#%W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$",
+ "W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$,+'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#$%%%&%R$0$*%m$S#S#}$m$y${$=%W$W$W$W$W$W$W$W$v f g y y #.v.F+%.$@i+j T 6.l 6.6.T C S R B j 4 4.4.4.4 (.&.S T =.7.F h.>.W.-+^+O 1 (#-%W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$;%",
+ ";%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$W$p$'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#>%,%'%H$o$)%$$S#S##$!$|$'$!%W$W$W$W$W$W$W$G.w N {.y ^.^+(+&.t+i+(.T 6.l l l 6.D k 5.S R &.&.&.&.R S C D =.7.F ~@G P @+#+3 $+g t@S+;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%",
+ "~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;% .'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#{%]%^%H$/%|$V#S#S#J#K#.%(%;%;%;%;%;%;%;%@@N y ^.].].n.V.*.u+j+4 T 6.=.=.=.=.l 6.D k C C 5.5.C k D l U 7.F ~@W v+m+_+V.j. +M d#;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%;%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%",
+ "~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%Q@'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#_%:%<%H$[%J$V#S#S#V#W#{$}%~%~%~%~%~%~%|%#.y e.c.].3.j.h.Y+A+j D l =.U E E U U =.l 6.6.6.6.l =.U m -.F ~@W l+8.=+.+:+s.]./#'#~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%",
+ "1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%$ '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#2%6$3%4%5%~$J#S#}$m$6%7%8%~%~%~%~%~%9%{.c.d.e.y $..+T L+6+g.D =.E m m 7.7.m m E E E E E m 7._.V n h.W l+~++@8+M+#+E.u '#* 1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%",
+ "1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%[+'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#0%a%b%c%d%y$J#S##$I$X#e%1%1%1%1%1%1%f%m.n.m.y 3.F.=+g.i+w.D U m 7._._.-.-._._._._._.-.V F n ~@;.W l+~+>.X W.r.y+L.'#g%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%1%",
+ "h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#D$s$i%c%j%k%J#}$J#y$l%m%h%h%h%h%h%n%m.M. +T.O +W.C /+w.T E 7._.V V F F F F F F n n %.~@h.W G l+~+8.7+(+_+#+$+-#'#* h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%",
+ "o%o%o%o%o%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%* '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#p%q%r%s%t%u%J#}$m$n$j$h%h%h%h%h%h%v%M.U.$+)+E.=+F p.g.C m _.V F n n %.%.%.%.~@~@h.;.W G e+v+~+8.P ++z V+r+u '#'#9+h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%h%o%o%o%o%o%o%o%",
+ "o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%p+'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#w%o%o%x%y%z%A%B%V#C%D%l%E%o%o%o%o%o%o%F% + +`.j.,.P E S k E V F n %.~@~@h.h.h.;.W W G e+l+v+~+8.H q.o.V+r+d '#'#y.o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%",
+ "G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%o%'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#* o%o%o%H%I%J%K%L%V#M%N%j$o%o%o%o%o%o%O%U.E.:+2+@+i.V U =.m V n ~@~@h.;.;.W W G G e+l+l+v+i.8.H m+=+8+#+< '#'#= G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%",
+ "G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%w%'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#-@G%G%G%G%P%Q%R%S%T%U%V%W%X%G%G%G%G%G%G%Y%$+3 9.o.q.+@~+%.V F %.h.;.;.W W G G e+l+l+v+~+i.8.P f+++z h t '#'#'#-@G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%G%",
+ "Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%= '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#-@Z%Z%Z%Z%Z%`% &.&+&@&#&$&j$Z%Z%Z%Z%Z%Z%Z%%&$+j.F+W.=+>.G ;.h.h.;.W W G G e+l+l+v+v+~+i.>.P m+:.*+F.'#'#'#w%D+Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%",
+ "Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%D+'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#w%Z%Z%Z%Z%Z%Z%Z%&&*&=&-&T%;&>&,&Z%Z%Z%Z%Z%Z%Z%'&)&r+M+++7++@>.l+G G G e+e+l+l+v+v+~+i.8.H +@7+++1+N '#'#'#w%u#Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%Z%",
+ "!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#w%!&!&!&!&!&!&!&!&~&{&]&^&/&(&_&!&!&!&!&!&!&!&!&!&:&;+9.z =+P i.~+~+l+l+l+v+v+~+~+i.>.>.+@m+Y T.'#'#'#'#w%H.!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&!&",
+ "<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&[&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#@ <&<&<&<&<&<&<&<&<&}&|&1&2&3&4&5&<&<&<&<&<&<&<&<&<&(@d F.n+m+m+f+8.~+i.i.~+i.8.8.H +@+@,.K.'#'#'#'#[&Y.<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&",
+ "<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&Y.'#'#'#'#'#'#'#b#b#2#1#2#c#6&t@u@b@l#b#'#'#'#'#[&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&J+*#y y+q.+@7+m+H >.f+8+(+<.: '#'#'#'#'#'#[&J+<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&<&",
+ "7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&'#'#'#'#'#c#8&V@O@`@y@g@q#J@O@{#2.g#W@g@p#1#'#'#'#S+7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&P#)&,.#+0#}#K.]#'#'#'#'#'#'#'#'#'#'#q p@7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&",
+ "7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&;$'#'#'#'#1#p#J@Z@g#L 6#N@j@V@-#0 b t {#l@E#2#'#'#'#,+9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&0&O._.h.O K@'#'#'#'#'#'#'#'#[&q q p@7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&7&",
+ "9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&S+'#'#'#'#2#l#i@(#s +.^# #V@V@Z@g#A.t h#v@s@2#'#'#'#'#9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&a&X k W.b&c&;$9&S+q S+p@S+p@9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&9&",
+ "9&9&9&9&9&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&+ '#'#'#'#2#s@l@=#b.a ]#W@V@V@ #^#t a h#z@b@b#'#'#'#'#5$d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&e&E.n 6.U.f&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&9&9&9&9&9&9&9&",
+ "d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&Q@'#'#'#'#'#b#t@x@h#S.t g#W@V@j@X@}#( ( |#M@u@b#'#'#'#'# .d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&g&q.l 8.u h&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&d&",
+ "i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&e$. '#'#'#'#'#c#d@K@}#a ~.0#Z@V@0 e /.8+*+8+1+E.+.1#'#'#'#'#@#i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&j&8+6.O.k&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&",
+ "i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&[+'#'#'#'#'#1#p#d@y@5#]#0#;#S.2+l 4.f.Q Q f.w.j C v+<+'#'#'#k#l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&m&f *+8.e.n&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&i&",
+ "l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&o&s@'#'#'#'#'#2#l#e@x@>#=#a P.=.4.Q z+5+5+5+|+A f.g.&.6.;.`.'#'# @l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&B$v V.y+A.p&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&",
+ "l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&C$v@X@'#'#'#'#'#c#t@i@N@=#| X 5.w.A z+5+6+b+L+L+b+|+f.4.*.6.n .+S.'#C$q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&r&>#e r+{.s&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&l&",
+ "q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&u&X@'#'#'#'#'#1#p#c@l@W@| i.C g.i |+z+6+c+Y+$@P+P+u+c+Q 4 C E h.q.8#l.t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&v&6#2 M.+.w&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&q&",
+ "t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&-@x@z@'#'#'#'#'#b#l#q#z@M 7+l j f.|+5+6+L+Y+P+B+B+B+B+H+Y+i B l V l+=+t j#t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&x&s O | ~ t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&q&",
+ "t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&;%z&X@2#'#'#'#'#1#b#t@g@2.8+-.*.C.5+L+b+b+k+A+B+B+B+B+B+B+B+k+w.C 7.;.H z b.y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&A&*#K.L.~.B&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&t&",
+ "y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&p+L@V@'#'#'#'#'#2#p#c@~#F+e+6.p.b+i+Y+d+d+Y+t+B+B+B+B+B+B+B+^@|+&.E ~@i.:.Q+C&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&4#]#1 1 !.D&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&",
+ "y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&~%c#/#u@'#'#'#'#'#b#E#e@d.q.F &.b+P+H+A+G+k+j+F&B+B+B+B+B+B+B+B+c+4.l %.v+7+z r+G&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&H&O@a m.b I&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&y&",
+ "E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&y.X@k@'#'#'#'#'#1#c#b@s r.i.U A H+B+B+^@Y+k+j+F&B+B+B+B+B+B+B+B+Y+p.T n v+7+(+.+K&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&L&}#[ {.J.M&J&J&J&J&J&J&J&J&J&J&J&J&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&E&",
+ "E&E&E&E&E&E&E&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&z#c#/#s@'#'#'#'#'#2#p#M@N.o.W R $@B+B+B+^@G+c+G+^@B+B+B+B+B+B+B+B+Y+D.k F v++@*+_+y+N&J&J&J&J&J&J&J&J&J&J&J&J&J&J&O&( ].d P&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&E&E&E&E&E&E&E&E&E&",
+ "J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&& w@W@'#'#'#'#'#'#b#l#2.F+q.n Q B+B+B+B+s+c+6+L+u+B+B+B+B+B+B+B+F&d+p.T F v+7+=+W.O.)+R&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&S&d 3.c.+.T&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&J&",
+ "Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&P#c#(#s@'#'#'#'#'#1#c#k@]..++@U G+B+B+B+B+j+5+A z+d+A+B+B+B+B+B+B+A+6+4.6.n ~+f+*+Y 9.3 V&U&U&U&U&U&U&U&U&U&U&U&U&W&[.:+e.9#X&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&",
+ "Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&* K@Z@'#'#'#'#'#'#2#d#{#[.@+>.5.^@B+B+B+H+c+i D.f.|+d+A+F&B+B+B+s+k+i R U h.8.m+(+W.r.2+M.Y&U&U&U&U&U&U&U&U&U&U&Z&n.8.F+w I&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&Q&",
+ "U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&p@b#Y@e@'#'#'#'#'#'#b#6&u <.n+~+j F&B+B+B+$@/+p.4 4 D./+b+G+u+u+i+c+/+4 k _.G H :.o.@+r.y+s.b.* ;$`&`&`&`&`&`&`&`& *(+7.Q+d .*`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&U&",
+ "U&U&U&U&U&U&U&U&U&U&U&U&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&+*= h@X@c#'#'#'#'#'#1#c#x@^.y+a+e+p.B+B+B+B+L+w.B *.*.(.g.f./+z+5+|+f.4.S U %.v++@++a+_+9.{+<+^.'#'#;$`&`&`&`&`&`&`&@*=.~@;+#*`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&U&U&U&U&U&U&U&U&U&U&U&U&U&U&",
+ "`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*5$2#z@d@'#'#'#'#'#'#2#d#=#v.{+a+G p.s+B+H+Y+A B C D D C *.(.4.p.p.4.B 5.=.V G >.m+*+n+V+,.{+;+v.|#'#'#S++*+*+*+*+*$*.+5.X {.%*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&`&",
+ "`&`&`&`&+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*G%= c@L@2#'#'#'#'#'#'#b#p#s T.{+a+G 4 k+s+Y+|+4 k =.m m U 6.T C S S 5.D U V h.~+7+X 8+W.M+h y+/.M.A.'#'#'#Y.&*&*&*&***>.m .+8#=*&*&*&*&*&*&*&*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*`&`&`&`&`&`&",
+ "+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*@#'#f@i@'#'#'#'#'#'#1#c#E#K.`.F+Y v+*.Q 6+A 4.C U -.F F V _.m U U U E _.n ;.v+P :.(+n+V+Q+V.r+;+T.x i@'#'#'#e$&*&*-*^+m 8.U.P&&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*",
+ "&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*Z%>*1#e@E#'#'#'#'#'#'#2#d#j@| +{+@+>.l j w.(.5.U F ~@;.W ;.~@%.n F n %.h.e+~+P q.=+a+W.M+,.F+2+/.U.#.~#'#'#'#'#e$;*,*Y =._+#.'*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*",
+ "&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*)*)*)*)*)*)*)*)*)*)*)*)*[#'#1#c@'#'#'#'#'#'#1#b#p#z@w +y+V++@V T C D E n W e+v+~+v+l+e+G G e+l+~+8.+@q.=+8+Y V+Q+O.{+<.<+U.{.g#'#'#'#'#'#C$!*7.G /.C+~*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&*",
+ ";*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*!&'#'#6&s@'#'#'#'#'#'#1#b#6&;#w U.2+Q+X l+F 7.-.~@G ~+>.H H H >.>.8.8.>.H +@f+:.=+8+Y _+.+,.#+:+3 ^+T.].@.'#'#'#'#'#{*.+R o.].]*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*;*",
+ ";*;*;*;*;*;*;*;*;*;*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*[#'#'#s@'#'#'#'#'#'#'#2#c#l#{#w T.<.,.8++@l+W G v+8.P 7+f+f+f+f+f+f+f+f+q.:.++(+a+Y _+M+9.V.{+'.P.s.N.].[ '#'#'#'#'#: >.7.O.~./*^*^*^*^*^*^*^*^*^*^*^*^*^*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*;*;*;*;*;*;*;*;*;*;*;*;*",
+ ")*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^ '#1#1#'#'#'#'#'#'#1#2#d#E#^#f M.E.#+@++++@H H +@f+q.:.X X X X X X ++++=+(+8+z Y _+M+9.O.F+:+j./.)+L.{.d l@'#'#'#M@s.6.+@ +(*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*)*",
+ ")*)*)*)*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*W$'#'#1#'#'#'#'#'#'#'#1#2#p#s@]#1 O <+:+Q+n+*+:.:.X ++*+(+(+(+(+(+(+o.o.8+a+z Y @+V+.+9.O.F+y+'.E.!+ +n.#.< /#'#'#'#s W.U Q+g :*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*)*)*)*)*)*)*",
+ "^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*q '#'#1#'#'#'#'#'#'#'#1#b#p#t@]#v d.s.<.O.1+Y 8+o.8+8+a+a+z z z z z n+n+Y W.@+V+M+Q+9.O.F+y+2+3 /.s.U.v.g c _#'#'#'#c.m ~+P.a [*<*<*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*",
+ "_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*7&'#'#1#1#'#'#'#'#'#'#'#1#b#p#t@^#e y +P.y+,.M+_+W.W.W.W.W.@+@+@+@+@+@+_+V+1+M+Q+r.,.O.F+y+r+j.P.!+$+N.e.w M }#'#'#Z@r.R n+3.*#<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*^*",
+ "_*_*_*_*_*_*_*_*_*_*_*_*_*_*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*u#'#'#2#'#'#'#'#'#'#'#'#1#b#p#t@6#< g N.[.<.-+h Q+M+M+1+1+1+1+1+1+1+1+M+.+.+Q+9.,.O.#+-+y+2+<.E.<+s.`.B.3.2 u ]#'#'#u H _.{+@.J@9&}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*",
+ "<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*|*z#'#2#b#'#'#'#'#'#'#'#'#1#2#d#s@=#M 2 v.$+;+2+-+O.,.9.r.r.r.r.r.r.r.r.9.9.,.h O.V.F+{+:+2+<.E./.[.$+T.O ^.v K.]#'#M@ +6.+@$+:#'#u#}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*<*",
+ "<*<*<*<*<*<*<*<*<*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*q '#8&I@'#'#'#'#'#'#'#'#'#2#d#E#5#b e c.T.[.E.2+y+F+#+V.O.h h h h h O.O.O.V.#+F+{+y+r+'.j.P./.^+)+$.L.d.N e @.]#'#2..+D O.w L@'#)@|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*<*<*<*<*<*<*<*<*<*<*<*",
+ "}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*'#2#H@6&'#'#'#'#'#'#'#'#'#2#c#6&K@_ c {.v. +!+E.'.:+y+{+F+F+F+#+#+#+F+F+-+-+{+:+r+2+<.3 P.<+^+F.`.N.v.y w c 8#6#'#y ~+H !++.u@'# .1*1*1*1*1*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*}*",
+ "}*}*}*}*}*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*i&'#d@l@'#'#'#'#'#'#'#'#'#'#1#b#p#s@a u f d.N.)+!+P.j.'.r+:+:+y+y+y+y+y+:+:+r+2+'.<.j.E.;+<+[.)+ +T.n.e.{.1 K.~.|#Z@9.R @+c.~#'#'# .1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*}*}*}*}*}*}*}*",
+ "|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*P#b#k@t@'#'#'#'#'#'#'#'#'#'#1#2#d#6&7#@.[ ].O U.)+^+;+E.3 <.'.'.2+2+2+'.'.'.<.j.3 E.;+<+!+s.)+`.T.B.m.y x e A.+.~#K.P -.'.< h@'#E# .2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*|*",
+ "|*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*3*3*l&'#d@k@'#'#'#'#'#'#'#'#'#'#'#'#1#b#d#:#t u 2 3.B.U.)+[.!+/.P.E.3 3 3 3 3 3 E.E.P.;+<+!+[.F.$+`.T.L.O c.#.2 c ~.J.}#$.l q.U..@E#I@f@p+2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*|*|*|*",
+ "1*1*1*1*1*1*1*1*1*1*1*1*1*1*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*D+b#v@6&'#'#'#'#'#'#'#'#'#'#'#'#'#2#b#y@+._ e N e.L.U.$+s.[.!+<+/.;+;+;+;+/./.<+!+^+[.s.)+ +$.N.L.v.d.].w e K.+.2.S.,.C h | y@J@c#'#(@3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*1*",
+ "2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*4*4*4*4*4*4*4*4*4*4*4*4*4*4*q&'#J@I@'#'#'#'#'#'#'#'#'#'#'#'#'#'#1#1#2#0 a u 1 ].e.n.T.`.$+F.s.[.^+^+^+^+^+^+[.s.F.)+ +$.U.M.B.O d.^.g 1 c 8#b.!.d.8.f+`.;#f@'#'#'#;$4*4*4*4*4*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*",
+ "2*2*2*2*2*2*2*2*2*2*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*|.8&j@c#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#1#=#b.8#c w ].e.n.M.U.`. +$+)+)+F.F.)+)+$+$+`.$.U.N.L.n.O c.^.N 2 [ b +.2.J.h S [.j@d#'#'#'#'#o%4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*2*2*2*2*2*2*2*2*2*2*2*2*",
+ "3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5$2#J@6&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#x@2.( @.} x ^.d.v.B.M.T.U.$.$.`.`.$.$.$.U.T.N.L.n.O e.c.].N f } : t s g#d 7+,.w@'#'#'#'#'#'#G%5*5*5*5*5*5*5*5*5*5*5*5*5*5*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*",
+ "3*3*3*3*3*3*3*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*y&# s@d@'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#0#s +.K.v x {.c.e.v.B.L.M.M.N.N.N.N.M.L.B.n.O m.d.y ].g f } c A.( 0 0 U.;+y@'#'#'#'#'#'## 5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*3*3*3*3*3*3*3*3*3*",
+ "4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*e$2#q#b## '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#Z@g#s ~.M } w #.^.c.e.O v.v.n.n.n.n.v.O m.e.d.3.^.#.g 2 } < b a 7#]#!.a q#'#'#'#'#'#'#'#J+6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*4*",
+ "4*4*4*4*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*7*7*7*7*7*7*7*7*7*7*E&'#t@s@'#e$# '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#=#!.s 8#M } f g {.^.3.c.d.e.e.d.d.d.c.3.y ].{.N w 1 } < K.t L ]#~# #d#'#'#'#'#'#'#'#'#Z%7*7*7*7*7*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*4*4*4*4*4*4*",
+ "5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*Q@p#b@'#l.7*S+'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#f@0#9#L _ u [ 1 f g #.{.].^.^.^.^.^.].{.#.g w 2 | [ c K.t b.g#N@u@'#'#'#'#'#'#'#'#'## 7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*",
+ "5*5*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*E&'#6&2## E&8*E&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#-#6#^#b.8#u d } 1 f w x g N N g g g w f 2 | e d : @.t b.0 }#'#'#'#'#'#'#'#'#'## S+E&8*8*8*8*8*8*8*8*8*8*8*8*8*8*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*6*5*5*5*5*",
+ "6*6*6*6*6*6*6*6*6*6*6*6*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*S+'#c#'#W$9*9*9*y.'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#j@:#|#]#J.a b : < e v | 1 2 2 2 1 1 | v e d M u A.~.b.0 6#_#'#'#'#'#'#'#'## S+!&8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*6*6*6*6*6*6*6*6*6*6*6*6*6*6*",
+ "7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*J&'#1#'#W+9*9*9*9*W$'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#W@=#:#]#s S._ b : c d [ e e e [ [ d c M u @.8#S.J.0 }#=#*#(#'#'#`@>#j@'#<&9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*7*6*",
+ "7*7*7*7*7*7*7*7*7*7*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*0*0*0*0*0*W+'#'#y.0*0*0*0*0*0*@ '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#I@/#5#_#0#0 b.a 8#@.b K.: : : : u K.b A.8#a ( s g#}#~#/#-#N ++=+N.L e@y.0*0*0*0*0*0*0*0*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*7*7*7*7*7*7*7*7*7*7*7*7*",
+ "8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*Q&'#'#@ Q&0*0*0*0*0*0*7&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#K@Y@(#5#|#9#7#J.S.a t 8#_ _ _ 8#t ~.S.( L 7#]#h#5#>#W@_#:+n F+y *#@ Q&0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*8*",
+ "8*8*8*8*8*8*8*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*a*a*a*a*a*a*a*a*a*a*a*;%'#'#;%a*a*a*a*a*a*a*a*H.'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#J@X@Z@-#/#:#0#g#7#L J.( ( ( ( ( J.L 2.0 9#6#:#;#W@`@L@~.=+e+P.[ v@;%a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*8*8*8*8*8*8*8*8*8*",
+ "9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*U&'#'#j#a*a*a*a*a*a*a*b*b*b*'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#V@K@X@ #W@*#_#}#^#9#!.0 0 0 0 !.9#^#}#{#5#># #O@M@K@n.%.n+n.s c*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*",
+ "9*9*9*9*9*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*;%'#H.b*b*b*b*b*b*b*b*b*b*b*9&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#t@V@x@M@L@X@Y@;#=#:#h#|#|#}#|#h#{#=#*#>#`@L@M@v@j@]#:+-.<.f d*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*9*9*9*9*9*9*9*",
+ "0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*H.H.e*e*e*e*e*e*e*e*e*e*e*e*e*d&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#8&I@j@w@v@z@L@`@Y@(#;#*#*#;#/#>#W@ #K@x@w@V@I@b@'#=#;+)+J.f*e*e*e*e*e*e*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*",
+ "0*0*0*0*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*`&e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*d&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#E#q#I@h@i@j@l@y@M@O@X@X@X@L@M@x@l@j@h@J@q#E#1#'#'#'#+ g*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*0*0*0*0*0*0*",
+ "a*a*a*a*a*a*a*a*a*a*a*a*a*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*d&'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#2#E#u@q#H@e@e@I@i@V@V@j@h@I@H@H@q#u@s@c#'#'#'#'#Y.+*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*",
+ "a*a*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*Y.'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#b#p#6&s@E#s@l#s@s@s@E#E#E#l#p#1#'#'#'#'#'#u#h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*b*a*a*a*a*",
+ "b*b*b*b*b*b*b*b*b*b*b*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*z#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#1#1#1#'#'#'#'#'#'#'#'#'#'#'#'#'#+ 1%i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*b*b*b*b*b*b*b*b*b*b*b*b*b*",
+ "b*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*j*j*j*j*j*j*j*j*j*j*j*j*j*l&+ '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#-@j*j*j*j*j*j*j*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*b*b*b*",
+ "e*e*e*e*e*e*e*e*e*e*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*h%+ '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#k*l*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*e*e*e*e*e*e*e*e*e*e*e*e*",
+ "h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*o%. '#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#n*o*t$p*q*r*s*m*m*m*m*m*m*m*m*m*m*m*m*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*h*e*",
+ "h*h*h*h*h*h*h*h*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*t*u**&a%v*'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#'#{%w*x*t$y*h$c$c$c$h$h$z*A*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*h*h*h*h*h*h*h*h*h*h*",
+ "i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*C*q*D*D*D*E*P$,%{&F*'#'#'#'#'#'#'#'#'#'#'#'#'#G*H*I*g$J*I%p*y*c$K*L*7$7$7$r%r%r%7$M*B*B*B*B*B*B*B*B*B*B*B*B*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*i*",
+ "i*i*i*i*i*i*i*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*N*N*N*N*O*<$K$P*Q*Q$R*S*7$L*r%b%3%T*;$;$;$)@)@)@;$G%t&G%U*V*W*X*%%D*t$h$K*7$S*Y*Y*i%R*b%b%b%b%Z*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*i*i*i*i*i*i*i*i*i*",
+ "j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*`* =.=+=v$@=@=v$#=,$$=8$Q$<$%=8$&=N*N*N*N*N*N*N*N*N*t&*===-= &Z$q*y%L*S*i%b%3%<%;=^%>=,=&%>='=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*j*",
+ "j*j*j*j*j*j*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*)=)=)=)=)=)=)=)=)=!=~=c${=]=R$9$^=/=+%^=9$v$(=+=_=+=:=)=)=)=)=)=)=)=)=)=)=<=[=Y$s$ &J*q*h$r%b%<%^%'%}=|=1=2=3=t$4=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*j*j*j*j*j*j*j*j*",
+ "m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=5=6=,%c$^%7=]=8=]$z$9=0=a=0=b=b=+%c=d=)=)=)=)=)=)=)=)=)=)=e=f=g=h=s$ &E*p*i=j=k=l=m=n=o=p=q=r=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*m*",
+ "m*m*m*m*m*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=t=u=*&v=y*i%&%%=1$w=H$x=y=w$'$b$o$[$z=s=s=s=s=s=s=s=s=s=s=s=y&A=B=_%>%k*-=C=D=E=x*F=O#G=y&s=s=s=s=s=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*m*m*m*m*m*m*m*",
+ "B*B*B*B*B*B*B*B*B*B*B*B*B*B*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=H=I=J=I%y%K*Y*;=;=u$%=K=_=@=z$L=0=#=M=s=s=s=s=s=s=s=s=s=s=s=s=s=<*G=N=H=f$O=t=O#y&s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*B*",
+ "B*B*B*B*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=e$Q=n*R=S=S=T=U=V=W=X=Y=Z=`= -.-+-@-#-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*B*B*B*B*B*B*",
+ "N*N*N*N*N*N*N*N*N*N*N*N*N*)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=9+$-%-&-*-=---n*;->-,-'-)-!-~-{-]-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=N*N*N*N*N*N*N*N*N*N*N*N*N*N*N*",
+ "N*N*N*N*)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-J&p@/-/-(-p@e$e$p@_-E@o@(-:-|*^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=)=N*N*N*N*N*N*",
+ ")=)=)=)=)=)=)=)=)=)=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=)=)=)=)=)=)=)=)=)=)=)=)=)=)=",
+ ")=)=)=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=s=)=)=)=)=)=",
+ "s=s=s=s=s=s=s=s=s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=s=s=s=s=s=s=s=s=",
+ "s=s=s=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=P=s=s=s=s=s=",
+ "P=P=P=P=P=P=P=P=P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P=P=P=P=P=P=P=P=P=",
+ "P=P=P=^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-}-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-^-P=P=P=P=P="
+ };
+
+#endif //IMAGE_H
diff --git a/krecipes/src/importers/Makefile.am b/krecipes/src/importers/Makefile.am
new file mode 100644
index 0000000..c0ac8a0
--- /dev/null
+++ b/krecipes/src/importers/Makefile.am
@@ -0,0 +1,15 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../backends $(all_includes)
+
+noinst_LTLIBRARIES=libkrecipesimporters.la
+libkrecipesimporters_la_SOURCES = mx2importer.cpp mmfimporter.cpp mxpimporter.cpp nycgenericimporter.cpp recipemlimporter.cpp baseimporter.cpp kreimporter.cpp rezkonvimporter.cpp kredbimporter.cpp
+libkrecipesimporters_la_METASOURCES=AUTO
+
+#the library search path.
+libkrecipesimporters_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+noinst_HEADERS = kreimporter.h
diff --git a/krecipes/src/importers/baseimporter.cpp b/krecipes/src/importers/baseimporter.cpp
new file mode 100644
index 0000000..2cc21a5
--- /dev/null
+++ b/krecipes/src/importers/baseimporter.cpp
@@ -0,0 +1,399 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "baseimporter.h"
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kprogress.h>
+#include <kmessagebox.h>
+
+#include <qvaluevector.h>
+
+#include "datablocks/recipe.h"
+#include "backends/recipedb.h"
+#include "datablocks/categorytree.h"
+#include "datablocks/unit.h"
+
+BaseImporter::BaseImporter() :
+ m_recipe_list( new RecipeList ),
+ m_cat_structure( 0 ),
+ file_recipe_count( 0 )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Import" );
+
+ direct = config->readBoolEntry( "DirectImport", false );
+}
+
+BaseImporter::~BaseImporter()
+{
+ delete m_recipe_list;
+ delete m_cat_structure;
+}
+
+void BaseImporter::add( const RecipeList &recipe_list )
+{
+ file_recipe_count += recipe_list.count();
+
+ for ( RecipeList::const_iterator it = recipe_list.begin(); it != recipe_list.end(); ++it ) {
+ Recipe copy = *it;
+ copy.recipeID = -1; //make sure an importer didn't give this a value
+ for ( RatingList::iterator rating_it = copy.ratingList.begin(); rating_it != copy.ratingList.end(); ++rating_it ) {
+ (*rating_it).id = -1;
+ }
+ m_recipe_list->append( copy );
+ }
+
+ if ( direct ) {
+ if ( !m_progress_dialog->wasCancelled() )
+ importRecipes( *m_recipe_list, m_database, m_progress_dialog );
+ }
+}
+
+void BaseImporter::add( const Recipe &recipe )
+{
+ file_recipe_count++;
+ Recipe copy = recipe;
+ copy.recipeID = -1; //make sure an importer didn't give this a value
+
+ if ( direct ) {
+ if ( !m_progress_dialog->wasCancelled() ) {
+ RecipeList list;
+ list.append( recipe );
+ importRecipes( list, m_database, m_progress_dialog );
+ }
+ }
+ else
+ m_recipe_list->append( copy );
+}
+
+void BaseImporter::parseFiles( const QStringList &filenames )
+{
+ if ( direct )
+ m_filenames = filenames;
+ else {
+ for ( QStringList::const_iterator file_it = filenames.begin(); file_it != filenames.end(); ++file_it ) {
+ file_recipe_count = 0;
+ parseFile( *file_it );
+ processMessages( *file_it );
+ }
+ }
+}
+
+void BaseImporter::import( RecipeDB *db, bool automatic )
+{
+ if ( direct ) {
+ m_database = db;
+
+ m_progress_dialog = new KProgressDialog( kapp->mainWidget(), 0,
+ i18n( "Importing selected recipes" ), QString::null, true );
+ KProgress *progress = m_progress_dialog->progressBar();
+ progress->setPercentageVisible(false);
+ progress->setTotalSteps( 0 );
+
+ for ( QStringList::const_iterator file_it = m_filenames.begin(); file_it != m_filenames.end(); ++file_it ) {
+ file_recipe_count = 0;
+ parseFile( *file_it );
+ processMessages( *file_it );
+
+ if ( m_progress_dialog->wasCancelled() )
+ break;
+ }
+
+ importUnitRatios( db );
+ delete m_progress_dialog;
+ }
+ else {
+ if ( m_recipe_list->count() == 0 )
+ return;
+
+ m_recipe_list->empty();
+ //db->blockSignals(true);
+
+ m_progress_dialog = new KProgressDialog( kapp->mainWidget(), 0,
+ i18n( "Importing selected recipes" ), QString::null, true );
+ KProgress *progress = m_progress_dialog->progressBar();
+ progress->setTotalSteps( m_recipe_list->count() );
+ progress->setFormat( i18n( "%v/%m Recipes" ) );
+
+ if ( m_cat_structure ) {
+ importCategoryStructure( db, m_cat_structure );
+ delete m_cat_structure;
+ m_cat_structure = 0;
+ }
+ importRecipes( *m_recipe_list, db, m_progress_dialog );
+ importUnitRatios( db );
+
+ //db->blockSignals(false);
+ delete m_progress_dialog; m_progress_dialog = 0;
+ }
+}
+
+void BaseImporter::importIngredient( IngredientData &ing, RecipeDB *db, KProgressDialog *progress_dialog )
+{
+ //cache some data we'll need
+ int max_units_length = db->maxUnitNameLength();
+ int max_group_length = db->maxIngGroupNameLength();
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ //create ingredient groups
+ QString real_group_name = ing.group.left( max_group_length );
+ int new_group_id = db->findExistingIngredientGroupByName(real_group_name);
+ if ( new_group_id == -1 ) {
+ db->createNewIngGroup( real_group_name );
+ new_group_id = db->lastInsertID();
+ }
+ ing.groupID = new_group_id;
+
+ int new_ing_id = db->findExistingIngredientByName(ing.name);
+ if ( new_ing_id == -1 && !ing.name.isEmpty() )
+ {
+ db->createNewIngredient( ing.name );
+ new_ing_id = db->lastInsertID();
+ }
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ Unit real_unit( ing.units.name.left( max_units_length ), ing.units.plural.left( max_units_length ) );
+ if ( real_unit.name.isEmpty() )
+ real_unit.name = real_unit.plural;
+ else if ( real_unit.plural.isEmpty() )
+ real_unit.plural = real_unit.name;
+
+ int new_unit_id = db->findExistingUnitByName(real_unit.name);
+ if ( new_unit_id == -1 ) {
+ db->createNewUnit( Unit(real_unit.name, real_unit.plural) );
+ new_unit_id = db->lastInsertID();
+ }
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ if ( ing.prepMethodList.count() > 0 ) {
+ for ( ElementList::iterator prep_it = ing.prepMethodList.begin(); prep_it != ing.prepMethodList.end(); ++prep_it ) {
+ int new_prep_id = db->findExistingPrepByName((*prep_it).name);
+ if ( new_prep_id == -1 ) {
+ db->createNewPrepMethod((*prep_it).name);
+ new_prep_id = db->lastInsertID();
+ }
+ (*prep_it).id = new_prep_id;
+ }
+ }
+
+ ing.units.id = new_unit_id;
+ ing.ingredientID = new_ing_id;
+
+ if ( !db->ingredientContainsUnit( new_ing_id, new_unit_id ) )
+ db->addUnitToIngredient( new_ing_id, new_unit_id );
+}
+
+void BaseImporter::importRecipes( RecipeList &selected_recipes, RecipeDB *db, KProgressDialog *progress_dialog )
+{
+ // Load Current Settings
+ KConfig *config = kapp->config();
+ config->setGroup( "Import" );
+ bool overwrite = config->readBoolEntry( "OverwriteExisting", false );
+
+ RecipeList::iterator recipe_it; RecipeList::iterator recipe_list_end( selected_recipes.end() );
+ RecipeList::iterator recipe_it_old = selected_recipes.end();
+ for ( recipe_it = selected_recipes.begin(); recipe_it != recipe_list_end; ++recipe_it ) {
+ if ( !direct ) {
+ if ( progress_dialog->wasCancelled() ) {
+ KMessageBox::information( kapp->mainWidget(), i18n( "All recipes up unto this point have been successfully imported." ) );
+ //db->blockSignals(false);
+ return ;
+ }
+ }
+
+ if ( recipe_it_old != selected_recipes.end() )
+ selected_recipes.remove( recipe_it_old );
+
+ progress_dialog->setLabel( QString( i18n( "Importing recipe: %1" ) ).arg( ( *recipe_it ).title ) );
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+
+ //add all recipe items (authors, ingredients, etc. to the database if they aren't already
+ IngredientList::iterator ing_list_end( ( *recipe_it ).ingList.end() );
+ for ( IngredientList::iterator ing_it = ( *recipe_it ).ingList.begin(); ing_it != ing_list_end; ++ing_it ) {
+ importIngredient( *ing_it, db, progress_dialog );
+
+ for ( QValueList<IngredientData>::iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it ) {
+ importIngredient( *sub_it, db, progress_dialog );
+ }
+ }
+
+ ElementList::iterator author_list_end( ( *recipe_it ).authorList.end() );
+ for ( ElementList::iterator author_it = ( *recipe_it ).authorList.begin(); author_it != author_list_end; ++author_it ) {
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ int new_author_id = db->findExistingAuthorByName(( *author_it ).name);
+ if ( new_author_id == -1 && !( *author_it ).name.isEmpty() ) {
+ db->createNewAuthor( ( *author_it ).name );
+ new_author_id = db->lastInsertID();
+ }
+
+ ( *author_it ).id = new_author_id;
+ }
+
+ ElementList::iterator cat_list_end( ( *recipe_it ).categoryList.end() );
+ for ( ElementList::iterator cat_it = ( *recipe_it ).categoryList.begin(); cat_it != cat_list_end; ++cat_it ) {
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ int new_cat_id = db->findExistingCategoryByName(( *cat_it ).name);
+ if ( new_cat_id == -1 && !( *cat_it ).name.isEmpty() ) {
+ db->createNewCategory( ( *cat_it ).name );
+ new_cat_id = db->lastInsertID();
+ }
+
+ ( *cat_it ).id = new_cat_id;
+ }
+
+ if ( !(*recipe_it).yield.type.isEmpty() ) {
+ int new_id = db->findExistingYieldTypeByName((*recipe_it).yield.type);
+ if ( new_id == -1 ) {
+ db->createNewYieldType( (*recipe_it).yield.type );
+ new_id = db->lastInsertID();
+ }
+ (*recipe_it).yield.type_id = new_id;
+ }
+
+ RatingList::iterator rating_list_end( ( *recipe_it ).ratingList.end() );
+ for ( RatingList::iterator rating_it = ( *recipe_it ).ratingList.begin(); rating_it != rating_list_end; ++rating_it ) {
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ for ( RatingCriteriaList::iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
+ int new_criteria_id = db->findExistingRatingByName(( *rc_it ).name);
+ if ( new_criteria_id == -1 && !( *rc_it ).name.isEmpty() ) {
+ db->createNewRating( ( *rc_it ).name );
+ new_criteria_id = db->lastInsertID();
+ }
+
+ ( *rc_it ).id = new_criteria_id;
+ }
+ }
+
+ if ( overwrite ) //overwrite existing
+ ( *recipe_it ).recipeID = db->findExistingRecipeByName( ( *recipe_it ).title );
+ else //rename
+ ( *recipe_it ).title = db->getUniqueRecipeTitle( ( *recipe_it ).title );
+
+ if ( direct ) {
+ progress_dialog->progressBar()->advance( 1 );
+ kapp->processEvents();
+ }
+
+ //save into the database
+ db->saveRecipe( &( *recipe_it ) );
+
+ recipe_it_old = recipe_it; //store to delete once we've got the next recipe
+ }
+}
+
+void BaseImporter::setCategoryStructure( CategoryTree *cat_structure )
+{
+ if ( direct ) {
+ importCategoryStructure( m_database, cat_structure );
+ }
+ else {
+ delete m_cat_structure;
+ m_cat_structure = cat_structure;
+ }
+}
+
+void BaseImporter::importCategoryStructure( RecipeDB *db, const CategoryTree *categoryTree )
+{
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ int new_cat_id = db->findExistingCategoryByName( child_it->category.name );
+ if ( new_cat_id == -1 ) {
+ db->createNewCategory( child_it->category.name, categoryTree->category.id );
+ new_cat_id = db->lastInsertID();
+ }
+
+ child_it->category.id = new_cat_id;
+
+ importCategoryStructure( db, child_it );
+ }
+}
+
+void BaseImporter::setUnitRatioInfo( UnitRatioList &ratioList, UnitList &unitList )
+{
+ m_ratioList = ratioList;
+ m_unitList = unitList;
+}
+
+void BaseImporter::importUnitRatios( RecipeDB *db )
+{
+ for ( UnitRatioList::const_iterator it = m_ratioList.begin(); it != m_ratioList.end(); ++it ) {
+ QString unitName1, unitName2;
+ for ( UnitList::const_iterator unit_it = m_unitList.begin(); unit_it != m_unitList.end(); ++unit_it ) {
+ if ( ( *it ).uID1 == ( *unit_it ).id ) {
+ unitName1 = ( *unit_it ).name;
+ if ( !unitName2.isNull() )
+ break;
+ }
+ else if ( ( *it ).uID2 == ( *unit_it ).id ) {
+ unitName2 = ( *unit_it ).name;
+ if ( !unitName1.isNull() )
+ break;
+ }
+ }
+
+ int unitId1 = db->findExistingUnitByName( unitName1 );
+ int unitId2 = db->findExistingUnitByName( unitName2 );
+
+ //the unit needed for the ratio may not have been added, because the
+ //recipes chosen did not include the unit
+ if ( unitId1 != -1 && unitId2 != -1 ) {
+ UnitRatio ratio;
+ ratio.uID1 = unitId1;
+ ratio.uID2 = unitId2;
+ ratio.ratio = ( *it ).ratio;
+ db->saveUnitRatio( &ratio );
+ }
+ }
+}
+
+void BaseImporter::processMessages( const QString &file )
+{
+ if ( m_error_msgs.count() > 0 ) {
+ //<!doc> ensures it is detected as RichText
+ m_master_error += QString( i18n( "<!doc>Import of recipes from the file <b>\"%1\"</b> <b>failed</b> due to the following error(s):" ) ).arg( file );
+ m_master_error += "<ul><li>" + m_error_msgs.join( "</li><li>" ) + "</li></ul>";
+
+ m_error_msgs.clear();
+ }
+ else if ( m_warning_msgs.count() > 0 ) {
+ m_master_warning += QString( i18n( "The file <b>%1</b> generated the following warning(s):" ) ).arg( file );
+ m_master_warning += "<ul><li>" + m_warning_msgs.join( "</li><li>" ) + "</li></ul>";
+
+ m_warning_msgs.clear();
+ }
+}
diff --git a/krecipes/src/importers/baseimporter.h b/krecipes/src/importers/baseimporter.h
new file mode 100644
index 0000000..2c68775
--- /dev/null
+++ b/krecipes/src/importers/baseimporter.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro <ugarro@users.sourceforge.net> *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef BASEIMPORTER_H
+#define BASEIMPORTER_H
+
+#include <klocale.h>
+
+#include <qstring.h>
+#include <qstringlist.h>
+
+#include "datablocks/recipelist.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/unitratiolist.h"
+
+class Recipe;
+class RecipeDB;
+class CategoryTree;
+class IngredientData;
+
+class KProgressDialog;
+
+/** @brief Subclass this class to create an importer for a specific file type.
+ *
+ * Subclasses should take the file name of the file to import in their constructor
+ * and then parse the file. For every recipe found in the file, a Recipe object should
+ * be created and added to the importer using the @ref add() function.
+ *
+ * @author Jason Kivlighn
+ */
+class BaseImporter
+{
+public:
+ BaseImporter();
+ virtual ~BaseImporter();
+
+ QString getMessages() const
+ {
+ return m_master_error + m_master_warning;
+ }
+ QString getErrorMsg() const
+ {
+ return m_master_error;
+ }
+ QString getWarningMsg() const
+ {
+ return m_master_warning;
+ }
+
+ void parseFiles( const QStringList &filenames );
+
+ /** Import all the recipes into the given database. These recipes are the
+ * recipes added to this class by a subclass using the @ref add() method.
+ */
+ void import( RecipeDB *db, bool automatic = false );
+
+ RecipeList recipeList() const { return *m_recipe_list; }
+ void setRecipeList( const RecipeList &list ) { *m_recipe_list = list; }
+
+ const CategoryTree *categoryStructure() const { return m_cat_structure; }
+
+protected:
+ virtual void parseFile( const QString &filename ) = 0;
+
+ void importRecipes( RecipeList &selected_recipes, RecipeDB *db, KProgressDialog *progess_dialog );
+
+ /** Add a recipe to be imported into the database */
+ void add( const Recipe &recipe );
+ void add( const RecipeList &recipe_list );
+
+ void setCategoryStructure( CategoryTree *cat_structure );
+ void setUnitRatioInfo( UnitRatioList &ratioList, UnitList &unitList );
+
+ int totalCount() const
+ {
+ return m_recipe_list->count();
+ }
+ int fileRecipeCount() const
+ {
+ return file_recipe_count;
+ }
+
+ void setErrorMsg( const QString & s )
+ {
+ m_error_msgs.append( s );
+ }
+ void addWarningMsg( const QString & s )
+ {
+ m_warning_msgs.append( s );
+ }
+
+private:
+ void importCategoryStructure( RecipeDB *, const CategoryTree * );
+ void importUnitRatios( RecipeDB * );
+ void importIngredient( IngredientData &ing, RecipeDB *db, KProgressDialog *progress_dialog );
+
+ void processMessages( const QString &file );
+
+ RecipeList *m_recipe_list;
+ CategoryTree *m_cat_structure;
+ UnitRatioList m_ratioList;
+ UnitList m_unitList;
+
+ QStringList m_warning_msgs;
+ QStringList m_error_msgs;
+ QString m_master_warning;
+ QString m_master_error;
+
+ int file_recipe_count;
+ bool direct;
+
+ RecipeDB *m_database;
+ KProgressDialog *m_progress_dialog;
+ QStringList m_filenames;
+};
+
+#endif //BASEIMPORTER_H
diff --git a/krecipes/src/importers/kredbimporter.cpp b/krecipes/src/importers/kredbimporter.cpp
new file mode 100644
index 0000000..2634462
--- /dev/null
+++ b/krecipes/src/importers/kredbimporter.cpp
@@ -0,0 +1,67 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "kredbimporter.h"
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+
+#include "datablocks/recipelist.h"
+#include "datablocks/categorytree.h"
+#include "backends/recipedb.h"
+
+KreDBImporter::KreDBImporter( const QString &_dbType, const QString &_host, const QString &_user, const QString &_pass, int _port ) : BaseImporter(),
+ dbType( _dbType ),
+ host( _host ),
+ user( _user ),
+ pass( _pass ),
+ port( _port )
+{}
+
+KreDBImporter::~KreDBImporter()
+{}
+
+void KreDBImporter::parseFile( const QString &file ) //this is either a database file or a database table
+{
+ RecipeDB * database = RecipeDB::createDatabase( dbType, host, user, pass, file, port, file ); //uses 'file' as either table or file name, depending on the database
+
+ if ( database ) {
+ database->connect( false ); //don't create the database if it fails to connect
+
+ if ( database->ok() ) {
+ //set the category structure
+ CategoryTree * tree = new CategoryTree;
+ database->loadCategories( tree );
+ setCategoryStructure( tree );
+
+ #if 0
+ //set unit ratios
+ UnitRatioList ratioList;
+ UnitList unitList;
+ database->loadUnitRatios( &ratioList );
+ database->loadUnits( &unitList );
+
+ setUnitRatioInfo( ratioList, unitList );
+ #endif
+
+ //now load recipes
+ RecipeList recipes;
+ database->loadRecipes( &recipes, RecipeDB::All ^ RecipeDB::Properties );
+
+ //now add these recipes to the importer
+ add( recipes );
+ }
+ else
+ setErrorMsg( database->err() );
+ }
+
+ delete database;
+}
diff --git a/krecipes/src/importers/kredbimporter.h b/krecipes/src/importers/kredbimporter.h
new file mode 100644
index 0000000..39a3ae1
--- /dev/null
+++ b/krecipes/src/importers/kredbimporter.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KREDBIMPORTER_H
+#define KREDBIMPORTER_H
+
+#include <qstring.h>
+
+#include "baseimporter.h"
+
+/** Class to import recipes from any other Krecipes database backend.
+ * Note: Though independant of database type, the two databases must have the same structure (i.e. be the same version)
+ * @author Jason Kivlighn
+ */
+class KreDBImporter : public BaseImporter
+{
+public:
+ KreDBImporter( const QString &dbType, const QString &host = QString::null, const QString &user = QString::null, const QString &pass = QString::null, int port = 0 );
+ virtual ~KreDBImporter();
+
+private:
+ virtual void parseFile( const QString &file_or_table );
+
+ QString dbType;
+ QString host;
+ QString user;
+ QString pass;
+ int port;
+};
+
+#endif //KREDBIMPORTER_H
diff --git a/krecipes/src/importers/kreimporter.cpp b/krecipes/src/importers/kreimporter.cpp
new file mode 100644
index 0000000..8676bf4
--- /dev/null
+++ b/krecipes/src/importers/kreimporter.cpp
@@ -0,0 +1,309 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "kreimporter.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <kstandarddirs.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/categorytree.h"
+
+KreImporter::KreImporter() : BaseImporter()
+{}
+
+void KreImporter::parseFile( const QString &filename )
+{
+ QFile * file = 0;
+ bool unlink = false;
+ kdDebug() << "loading file: %s" << filename << endl;
+
+ if ( filename.right( 4 ) == ".kre" ) {
+ file = new QFile( filename );
+ kdDebug() << "file is an archive" << endl;
+ KTar* kre = new KTar( filename, "application/x-gzip" );
+ kre->open( IO_ReadOnly );
+ const KArchiveDirectory* dir = kre->directory();
+ QString name;
+ QStringList fileList = dir->entries();
+ for ( QStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it ) {
+ if ( ( *it ).right( 6 ) == ".kreml" ) {
+ name = *it;
+ }
+ }
+ if ( name.isEmpty() ) {
+ kdDebug() << "error: Archive doesn't contain a valid Krecipes file" << endl;
+ setErrorMsg( i18n( "Archive does not contain a valid Krecipes file" ) );
+ return ;
+ }
+ QString tmp_dir = locateLocal( "tmp", "" );
+ dir->copyTo( tmp_dir );
+ file = new QFile( tmp_dir + name );
+ kre->close();
+ unlink = true; //remove file after import
+ }
+ else {
+ file = new QFile( filename );
+ }
+
+ if ( file->open( IO_ReadOnly ) ) {
+ kdDebug() << "file opened" << endl;
+ QDomDocument doc;
+ QString error;
+ int line;
+ int column;
+ if ( !doc.setContent( file, &error, &line, &column ) ) {
+ kdDebug() << QString( "error: \"%1\" at line %2, column %3" ).arg( error ).arg( line ).arg( column ) << endl;
+ setErrorMsg( QString( i18n( "\"%1\" at line %2, column %3" ) ).arg( error ).arg( line ).arg( column ) );
+ return ;
+ }
+
+ QDomElement kreml = doc.documentElement();
+
+ if ( kreml.tagName() != "krecipes" ) {
+ setErrorMsg( i18n( "This file does not appear to be a *.kreml file" ) );
+ return ;
+ }
+
+ // TODO Check if there are changes between versions
+ QString kreVersion = kreml.attribute( "version" );
+ kdDebug() << QString( i18n( "KreML version %1" ) ).arg( kreVersion ) << endl;
+
+ QDomNodeList r = kreml.childNodes();
+ QDomElement krecipe;
+
+ for ( unsigned z = 0; z < r.count(); z++ ) {
+ krecipe = r.item( z ).toElement();
+ QDomNodeList l = krecipe.childNodes();
+ if ( krecipe.tagName() == "krecipes-recipe" ) {
+ Recipe recipe;
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ if ( el.tagName() == "krecipes-description" ) {
+ readDescription( el.childNodes(), &recipe );
+ }
+ if ( el.tagName() == "krecipes-ingredients" ) {
+ readIngredients( el.childNodes(), &recipe );
+ }
+ if ( el.tagName() == "krecipes-instructions" ) {
+ recipe.instructions = el.text().stripWhiteSpace();
+ }
+ if ( el.tagName() == "krecipes-ratings" ) {
+ readRatings( el.childNodes(), &recipe );
+ }
+ }
+ add
+ ( recipe );
+ }
+ else if ( krecipe.tagName() == "krecipes-category-structure" ) {
+ CategoryTree * tree = new CategoryTree;
+ readCategoryStructure( l, tree );
+ setCategoryStructure( tree );
+ }
+ }
+ }
+ if ( unlink ) {
+ file->remove
+ ();
+ }
+}
+
+KreImporter::~KreImporter()
+{
+}
+
+void KreImporter::readCategoryStructure( const QDomNodeList& l, CategoryTree *tree )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+
+ QString category = el.attribute( "name" );
+ CategoryTree *child_node = tree->add
+ ( Element( category ) );
+ readCategoryStructure( el.childNodes(), child_node );
+ }
+}
+
+void KreImporter::readDescription( const QDomNodeList& l, Recipe *recipe )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ if ( el.tagName() == "title" ) {
+ recipe->title = el.text();
+ kdDebug() << "Found title: " << recipe->title << endl;
+ }
+ else if ( el.tagName() == "author" ) {
+ kdDebug() << "Found author: " << el.text() << endl;
+ recipe->authorList.append( Element( el.text() ) );
+ }
+ else if ( el.tagName() == "serving" ) { //### Keep for < 0.9 compatibility
+ recipe->yield.amount = el.text().toInt();
+ }
+ else if ( el.tagName() == "yield" ) {
+ QDomNodeList yield_children = el.childNodes();
+ for ( unsigned j = 0; j < yield_children.count(); j++ ) {
+ QDomElement y = yield_children.item( j ).toElement();
+ if ( y.tagName() == "amount" )
+ readAmount(y,recipe->yield.amount,recipe->yield.amount_offset);
+ else if ( y.tagName() == "type" )
+ recipe->yield.type = y.text();
+ }
+ }
+ else if ( el.tagName() == "preparation-time" ) {
+ recipe->prepTime = QTime::fromString( el.text() );
+ }
+ else if ( el.tagName() == "category" ) {
+ QDomNodeList categories = el.childNodes();
+ for ( unsigned j = 0; j < categories.count(); j++ ) {
+ QDomElement c = categories.item( j ).toElement();
+ if ( c.tagName() == "cat" ) {
+ kdDebug() << "Found category: " << QString( c.text() ).stripWhiteSpace() << endl;
+ recipe->categoryList.append( Element( QString( c.text() ).stripWhiteSpace() ) );
+ }
+ }
+ }
+ else if ( el.tagName() == "pictures" ) {
+ if ( el.hasChildNodes() ) {
+ QDomNodeList pictures = el.childNodes();
+ for ( unsigned j = 0; j < pictures.count(); j++ ) {
+ QDomElement pic = pictures.item( j ).toElement();
+ QCString decodedPic;
+ if ( pic.tagName() == "pic" )
+ kdDebug() << "Found photo" << endl;
+ QPixmap pix;
+ KCodecs::base64Decode( QCString( pic.text().latin1() ), decodedPic );
+ int len = decodedPic.size();
+ QByteArray picData( len );
+ memcpy( picData.data(), decodedPic.data(), len );
+ bool ok = pix.loadFromData( picData, "JPEG" );
+ if ( ok ) {
+ recipe->photo = pix;
+ }
+ }
+ }
+ }
+ }
+}
+
+void KreImporter::readIngredients( const QDomNodeList& l, Recipe *recipe, const QString &header, Ingredient *ing )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ if ( el.tagName() == "ingredient" ) {
+ QDomNodeList ingredient = el.childNodes();
+ Ingredient new_ing;
+ for ( unsigned j = 0; j < ingredient.count(); j++ ) {
+ QDomElement ing = ingredient.item( j ).toElement();
+ if ( ing.tagName() == "name" ) {
+ new_ing.name = QString( ing.text() ).stripWhiteSpace();
+ kdDebug() << "Found ingredient: " << new_ing.name << endl;
+ }
+ else if ( ing.tagName() == "amount" ) {
+ readAmount(ing,new_ing.amount,new_ing.amount_offset);
+ }
+ else if ( ing.tagName() == "unit" ) {
+ new_ing.units = Unit( ing.text().stripWhiteSpace(), new_ing.amount+new_ing.amount_offset );
+ }
+ else if ( ing.tagName() == "prep" ) {
+ new_ing.prepMethodList = ElementList::split(",",QString( ing.text() ).stripWhiteSpace());
+ }
+ else if ( ing.tagName() == "substitutes" ) {
+ readIngredients(ing.childNodes(), recipe, header, &new_ing);
+ }
+ }
+ new_ing.group = header;
+
+ if ( !ing )
+ recipe->ingList.append( new_ing );
+ else
+ ing->substitutes.append( new_ing );
+ }
+ else if ( el.tagName() == "ingredient-group" ) {
+ readIngredients( el.childNodes(), recipe, el.attribute( "name" ) );
+ }
+ }
+}
+
+void KreImporter::readAmount( const QDomElement& amountEl, double &amount, double &amount_offset )
+{
+ QDomNodeList children = amountEl.childNodes();
+
+ double min = 0,max = 0;
+ for ( unsigned i = 0; i < children.count(); i++ ) {
+ QDomElement child = children.item( i ).toElement();
+ if ( child.tagName() == "min" ) {
+ min = ( QString( child.text() ).stripWhiteSpace() ).toDouble();
+ }
+ else if ( child.tagName() == "max" )
+ max = ( QString( child.text() ).stripWhiteSpace() ).toDouble();
+ else if ( child.tagName().isEmpty() )
+ min = ( QString( amountEl.text() ).stripWhiteSpace() ).toDouble();
+ }
+
+ amount = min;
+ if ( max > 0 )
+ amount_offset = max-min;
+}
+
+void KreImporter::readRatings( const QDomNodeList& l, Recipe *recipe )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement child = l.item( i ).toElement();
+ if ( child.tagName() == "rating" ) {
+ Rating r;
+
+ QDomNodeList ratingChildren = child.childNodes();
+ for ( unsigned j = 0; j < ratingChildren.count(); j++ ) {
+ QDomElement ratingChild = ratingChildren.item( j ).toElement();
+ if ( ratingChild.tagName() == "comment" ) {
+ r.comment = ratingChild.text();
+ }
+ else if ( ratingChild.tagName() == "rater" ) {
+ r.rater = ratingChild.text();
+ }
+ else if ( ratingChild.tagName() == "criterion" ) {
+ readCriterion(ratingChild.childNodes(),r.ratingCriteriaList);
+ }
+ }
+ recipe->ratingList.append(r);
+ }
+ }
+}
+
+void KreImporter::readCriterion( const QDomNodeList& l, RatingCriteriaList &rc_list )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement child = l.item( i ).toElement();
+
+ if ( child.tagName() == "criteria" ) {
+ RatingCriteria rc;
+
+ QDomNodeList criteriaChildren = child.childNodes();
+ for ( unsigned j = 0; j < criteriaChildren.count(); j++ ) {
+ QDomElement criteriaChild = criteriaChildren.item( j ).toElement();
+
+ if ( criteriaChild.tagName() == "name" ) {
+ rc.name = criteriaChild.text();
+ }
+ else if ( criteriaChild.tagName() == "stars" ) {
+ rc.stars = criteriaChild.text().toDouble();
+ }
+ }
+ rc_list.append(rc);
+ }
+ }
+}
diff --git a/krecipes/src/importers/kreimporter.h b/krecipes/src/importers/kreimporter.h
new file mode 100644
index 0000000..73d6d13
--- /dev/null
+++ b/krecipes/src/importers/kreimporter.h
@@ -0,0 +1,54 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KREIMPORTER_H
+#define KREIMPORTER_H
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kmdcodec.h>
+#include <ktar.h>
+#include <ktempfile.h>
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include "baseimporter.h"
+
+#include "datablocks/recipe.h"
+
+class Recipe;
+class CategoryTree;
+
+/**
+Import for Krecipes native file format (.kre, .kreml)
+
+@author Cyril Bosselut, Jason Kivlighn
+*/
+class KreImporter: public BaseImporter
+{
+public:
+ KreImporter();
+ virtual ~KreImporter();
+
+private:
+ void parseFile( const QString& filename );
+
+private:
+ void readCategoryStructure( const QDomNodeList& l, CategoryTree *tree );
+ void readDescription( const QDomNodeList& l, Recipe* );
+ void readIngredients( const QDomNodeList& l, Recipe*, const QString &header = QString::null, Ingredient *ing = 0 );
+ void readAmount( const QDomElement& amount1, double &amount2, double &amount_offset );
+ void readRatings( const QDomNodeList&, Recipe * );
+ void readCriterion( const QDomNodeList&, RatingCriteriaList &r );
+};
+
+#endif
diff --git a/krecipes/src/importers/mmfimporter.cpp b/krecipes/src/importers/mmfimporter.cpp
new file mode 100644
index 0000000..ab6b7eb
--- /dev/null
+++ b/krecipes/src/importers/mmfimporter.cpp
@@ -0,0 +1,336 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "mmfimporter.h"
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qregexp.h>
+#include <qtextstream.h>
+#include <qstringlist.h>
+
+#include "datablocks/mixednumber.h"
+#include "datablocks/recipe.h"
+#include "mmdata.h"
+
+//TODO: pre-parse file and try to correct alignment errors in ingredients?
+
+MMFImporter::MMFImporter() : BaseImporter()
+{}
+
+MMFImporter::~MMFImporter()
+{}
+
+void MMFImporter::parseFile( const QString &file )
+{
+ resetVars();
+
+ QFile input( file );
+
+ if ( input.open( IO_ReadOnly ) ) {
+ QTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ QString line;
+ while ( !stream.atEnd() ) {
+ line = stream.readLine();
+
+ if ( line.startsWith( "MMMMM" ) ) {
+ version = VersionMMMMM;
+ importMMF( stream );
+ }
+ else if ( line.contains( "Recipe Extracted from Meal-Master (tm) Database" ) ) {
+ version = FromDatabase;
+ importMMF( stream );
+ }
+ else if ( line.startsWith( "-----" ) ) {
+ version = VersionNormal;
+ importMMF( stream );
+ }
+ else if ( line.startsWith( "MM" ) ) {
+ version = VersionBB;
+ ( void ) stream.readLine();
+ importMMF( stream );
+ }
+
+ stream.skipWhiteSpace();
+ }
+
+ if ( fileRecipeCount() == 0 )
+ addWarningMsg( i18n( "No recipes found in this file." ) );
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+void MMFImporter::importMMF( QTextStream &stream )
+{
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ QString current;
+
+ //===============FIXED FORMAT================//
+ //line 1: title
+ //line 2: categories (comma or space separated)
+ //line 3: yield (number followed by label)
+
+ //title
+ stream.skipWhiteSpace();
+ current = stream.readLine();
+ m_title = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ kdDebug() << "Found title: " << m_title << endl;
+
+ //categories
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ const char separator = ( version == FromDatabase ) ? ' ' : ',';
+ QStringList categories = QStringList::split( separator, current.mid( current.find( ":" ) + 1, current.length() ) );
+ for ( QStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat;
+ new_cat.name = QString( *it ).stripWhiteSpace();
+ kdDebug() << "Found category: " << new_cat.name << endl;
+ m_categories.append( new_cat );
+ }
+
+ //servings
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ if ( current.startsWith( "Yield:" ) ) {
+ //get the number between the ":" and the next space after it
+ m_servings = current.mid( current.find( ":" ) + 1,
+ current.find( " ", current.find( ":" ) + 2 ) - current.find( ":" ) ).toInt();
+ kdDebug() << "Found yield: " << m_servings << endl;
+ }
+ else if ( current.startsWith( "Servings:" ) ) //from database version
+ {
+ m_servings = current.mid( current.find( ":" ) + 1, current.length() ).toInt();
+ kdDebug() << "Found servings: " << m_servings << endl;
+ }
+
+ //=======================VARIABLE FORMAT===================//
+ //read lines until ending is found
+ //each line is either an ingredient, ingredient header, or instruction
+ bool instruction_found = false;
+ bool is_sub = false;
+
+ ( void ) stream.readLine();
+ current = stream.readLine();
+ while ( current.stripWhiteSpace() != "MMMMM" &&
+ current.stripWhiteSpace() != "-----" &&
+ current.stripWhiteSpace() != "-----------------------------------------------------------------------------" &&
+ !stream.atEnd() ) {
+ bool col_one_used = loadIngredientLine( current.left( 41 ), m_left_col_ing, is_sub );
+ if ( col_one_used ) //only check for second column if there is an ingredient in the first column
+ loadIngredientLine( current.mid( 41, current.length() ), m_right_col_ing, is_sub );
+
+ if ( instruction_found && col_one_used ) {
+ addWarningMsg( QString( i18n( "While loading recipe <b>%1</b> "
+ "an ingredient line was found after the directions. "
+ "While this is valid, it most commonly indicates an incorrectly "
+ "formatted recipe." ) ).arg( m_title ) );
+ }
+
+ if ( !col_one_used &&
+ !loadIngredientHeader( current.stripWhiteSpace() ) ) {
+ if ( !current.stripWhiteSpace().isEmpty() )
+ instruction_found = true;
+ m_instructions += current.stripWhiteSpace() + "\n";
+ //kdDebug()<<"Found instruction line: "<<current.stripWhiteSpace()<<endl;
+ }
+
+ current = stream.readLine();
+ }
+ m_instructions = m_instructions.stripWhiteSpace();
+ //kdDebug()<<"Found instructions: "<<m_instructions<<endl;
+
+ putDataInRecipe();
+}
+
+bool MMFImporter::loadIngredientLine( const QString &string, IngredientList &list, bool &is_sub )
+{
+ //just ignore an empty line
+ if ( string.stripWhiteSpace().isEmpty() )
+ return false;
+
+ Ingredient new_ingredient;
+ new_ingredient.amount = 0; //amount not required, so give default of 0
+
+ if ( string.at( 11 ) == "-" && string.mid( 0, 11 ).stripWhiteSpace().isEmpty() && !list.isEmpty() ) //continuation of previous ingredient
+ {
+ //kdDebug()<<"Appending to last ingredient in column: "<<string.stripWhiteSpace().mid(1,string.length())<<endl;
+ ( *list.at( list.count() - 1 ) ).name += " " + string.stripWhiteSpace().mid( 1, string.length() );
+ QString name = ( *list.at( list.count() - 1 ) ).name;
+
+ if ( name.endsWith(", or") ) {
+ ( *list.at( list.count() - 1 ) ).name = name.left(name.length()-4);
+ is_sub = true;
+ }
+ else
+ is_sub = false;
+
+ return true;
+ }
+
+ //amount
+ if ( !string.mid( 0, 7 ).stripWhiteSpace().isEmpty() ) {
+ bool ok;
+ MixedNumber amount = MixedNumber::fromString( string.mid( 0, 7 ).stripWhiteSpace(), &ok, false );
+ if ( !ok )
+ return false;
+ else
+ new_ingredient.amount = amount.toDouble();
+ }
+
+ //amount/unit separator
+ if ( string[ 7 ] != ' ' )
+ return false;
+
+ //unit
+ if ( !string.mid( 8, 2 ).stripWhiteSpace().isEmpty() ) {
+ bool is_unit = false;
+ QString unit( string.mid( 8, 2 ).stripWhiteSpace() );
+ for ( int i = 0; unit_info[ i ].short_form; i++ ) {
+ if ( unit_info[ i ].short_form == unit ) {
+ is_unit = true;
+ if ( new_ingredient.amount <= 1 )
+ unit = unit_info[ i ].expanded_form;
+ else
+ unit = unit_info[ i ].plural_expanded_form;
+
+ break;
+ }
+ }
+ if ( !is_unit ) { /*This gives too many false warnings...
+ addWarningMsg( QString(i18n("Unit \"%1\" not recognized. "
+ "Used in the context of \"%2\". If this shouldn't be an ingredient line (i.e. is part of the instructions), "
+ "then you can safely ignore this warning, and the recipe will be correctly imported.")).arg(unit).arg(string.stripWhiteSpace()) );*/
+ return false;
+ }
+
+ if ( int(new_ingredient.amount) > 1 )
+ new_ingredient.units.plural = unit;
+ else
+ new_ingredient.units.name = unit;
+ }
+
+ //unit/name separator
+ if ( string[ 10 ] != ' ' || string[ 11 ] == ' ' )
+ return false;
+
+ //name and preparation method
+ new_ingredient.name = string.mid( 11, 41 ).stripWhiteSpace();
+
+ //put in the header... it there is no header, current_header will be QString::null
+ new_ingredient.group = current_header;
+
+ bool last_is_sub = is_sub;
+ if ( new_ingredient.name.endsWith(", or") ) {
+ new_ingredient.name = new_ingredient.name.left(new_ingredient.name.length()-4);
+ is_sub = true;
+ }
+ else
+ is_sub = false;
+
+ if ( last_is_sub )
+ ( *list.at( list.count() - 1 ) ).substitutes.append(new_ingredient);
+ else
+ list.append( new_ingredient );
+
+ //if we made it this far it is an ingredient line
+ return true;
+}
+
+bool MMFImporter::loadIngredientHeader( const QString &string )
+{
+ if ( ( string.startsWith( "-----" ) || string.startsWith( "MMMMM" ) ) &&
+ string.length() >= 40 &&
+ ( ( string.at( string.length() / 2 ) != "-" ) ||
+ ( string.at( string.length() / 2 + 1 ) != "-" ) ||
+ ( string.at( string.length() / 2 - 1 ) != "-" ) ) ) {
+ QString header( string.stripWhiteSpace() );
+
+ //get only the header name
+ header.remove( QRegExp( "^MMMMM" ) );
+ header.remove( QRegExp( "^-*" ) ).remove( QRegExp( "-*$" ) );
+
+ kdDebug() << "found ingredient header: " << header << endl;
+
+ //merge all columns before appending to full ingredient list to maintain the ingredient order
+ for ( IngredientList::iterator ing_it = m_left_col_ing.begin(); ing_it != m_left_col_ing.end(); ++ing_it ) {
+ m_all_ing.append( *ing_it );
+ }
+ m_left_col_ing.empty();
+
+ for ( IngredientList::iterator ing_it = m_right_col_ing.begin(); ing_it != m_right_col_ing.end(); ++ing_it ) {
+ m_all_ing.append( *ing_it );
+ }
+ m_right_col_ing.empty();
+
+ current_header = header;
+ return true;
+ }
+ else
+ return false;
+}
+
+void MMFImporter::putDataInRecipe()
+{
+ for ( IngredientList::const_iterator ing_it = m_left_col_ing.begin(); ing_it != m_left_col_ing.end(); ++ing_it )
+ m_all_ing.append( *ing_it );
+ for ( IngredientList::const_iterator ing_it = m_right_col_ing.begin(); ing_it != m_right_col_ing.end(); ++ing_it )
+ m_all_ing.append( *ing_it );
+
+ for ( IngredientList::iterator ing_it = m_all_ing.begin(); ing_it != m_all_ing.end(); ++ing_it ) {
+ QString name_and_prep = ( *ing_it ).name;
+ int separator_index = name_and_prep.find( QRegExp( "(;|,)" ) );
+ if ( separator_index != -1 ) {
+ ( *ing_it ).name = name_and_prep.mid( 0, separator_index ).stripWhiteSpace();
+ ( *ing_it ).prepMethodList = ElementList::split(",",name_and_prep.mid( separator_index + 1, name_and_prep.length() ).stripWhiteSpace() );
+ }
+ }
+
+ //create the recipe
+ Recipe new_recipe;
+ new_recipe.yield.amount = m_servings;
+ new_recipe.yield.type = i18n("servings");
+ new_recipe.title = m_title;
+ new_recipe.instructions = m_instructions;
+ new_recipe.ingList = m_all_ing;
+ new_recipe.categoryList = m_categories;
+ new_recipe.authorList = m_authors;
+ new_recipe.recipeID = -1;
+
+ //put it in the recipe list
+ add
+ ( new_recipe );
+
+ //reset for the next recipe to use these variables
+ resetVars();
+}
+
+void MMFImporter::resetVars()
+{
+ m_left_col_ing.empty();
+ m_right_col_ing.empty();
+ m_all_ing.empty();
+ m_authors.clear();
+ m_categories.clear();
+
+ m_servings = 0;
+
+ m_title = QString::null;
+ m_instructions = QString::null;
+
+ current_header = QString::null;
+}
+
diff --git a/krecipes/src/importers/mmfimporter.h b/krecipes/src/importers/mmfimporter.h
new file mode 100644
index 0000000..bf89d44
--- /dev/null
+++ b/krecipes/src/importers/mmfimporter.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef MMFIMPORTER_H
+#define MMFIMPORTER_H
+
+#include <qstring.h>
+
+#include "baseimporter.h"
+#include "datablocks/ingredientlist.h"
+#include "datablocks/elementlist.h"
+
+/** Class to import Meal-Master's MMF (Meal-Master Format) file format.
+ * @author Jason Kivlighn
+ */
+class MMFImporter : public BaseImporter
+{
+public:
+ MMFImporter();
+ virtual ~MMFImporter();
+
+private:
+ enum FormatVersion { FromDatabase, VersionMMMMM, VersionBB, VersionNormal };
+
+ virtual void parseFile( const QString &filename );
+
+ void importMMF( QTextStream &stream );
+
+ /** Parses the line and save it if the line is a valid ingredient and return true.
+ * Returns false if not an ingredient.
+ */
+ bool loadIngredientLine( const QString &, IngredientList &, bool &is_sub );
+
+ /** Parses the line and save it if the line is a valid ingredient header and return true.
+ * Returns false if not an ingredient header.
+ */
+ bool loadIngredientHeader( const QString & );
+
+ void resetVars();
+ void putDataInRecipe();
+
+ int m_servings;
+
+ QString m_instructions;
+ QString m_title;
+
+ ElementList m_authors;
+ ElementList m_categories;
+ IngredientList m_left_col_ing;
+ IngredientList m_right_col_ing;
+ IngredientList m_all_ing;
+
+ FormatVersion version;
+
+ QString current_header;
+};
+
+#endif //MMFIMPORTER_H
diff --git a/krecipes/src/importers/mx2importer.cpp b/krecipes/src/importers/mx2importer.cpp
new file mode 100644
index 0000000..3c949b9
--- /dev/null
+++ b/krecipes/src/importers/mx2importer.cpp
@@ -0,0 +1,186 @@
+/*
+Copyright (C) 2003 Richard Lrkng
+Copyright (C) 2003 Jason Kivlighn
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA
+*/
+
+#include "mx2importer.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <qtextstream.h>
+#include <qdatetime.h>
+
+#include "datablocks/recipe.h"
+
+
+MX2Importer::MX2Importer()
+{}
+
+void MX2Importer::parseFile( const QString& filename )
+{
+ QFile file( filename );
+ kdDebug() << "loading file: " << filename << endl;
+ if ( file.open( IO_ReadOnly ) ) {
+ kdDebug() << "file opened" << endl;
+ QDomDocument doc;
+
+ //hopefully a temporary hack, since MasterCook creates invalid xml declarations
+ QTextStream stream( &file );
+ QString all_data = stream.read();
+ if ( all_data.startsWith( "<?xml" ) )
+ all_data.remove( 0, all_data.find( "?>" ) + 2 );
+
+ QString error;
+ int line;
+ int column;
+ if ( !doc.setContent( all_data, &error, &line, &column ) ) {
+ kdDebug() << QString( "error: \"%1\" at line %2, column %3" ).arg( error ).arg( line ).arg( column ) << endl;
+ setErrorMsg( QString( i18n( "\"%1\" at line %2, column %3. This may not be a *.mx2 file." ) ).arg( error ).arg( line ).arg( column ) );
+ return ;
+ }
+
+ QDomElement mx2 = doc.documentElement();
+
+ // TODO Check if there are changes between versions
+ if ( mx2.tagName() != "mx2" /*|| mx2.attribute("source") != "MasterCook 5.0"*/ ) {
+ setErrorMsg( i18n( "This file does not appear to be a *.mx2 file" ) );
+ return ;
+ }
+
+ QDomNodeList l = mx2.childNodes();
+
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+
+ if ( el.tagName() == "RcpE" ) {
+ Recipe recipe;
+ recipe.title = el.attribute( "name" );
+
+ Element author( el.attribute( "author" ) );
+ recipe.authorList.append( author );
+
+ readRecipe( el.childNodes(), &recipe );
+ add
+ ( recipe );
+ }
+ }
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+MX2Importer::~MX2Importer()
+{
+}
+
+void MX2Importer::readRecipe( const QDomNodeList& l, Recipe *recipe )
+{
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+
+ QString tagName = el.tagName();
+ if ( tagName == "Serv" ) {
+ recipe->yield.amount = el.attribute( "qty" ).toInt();
+ recipe->yield.type = i18n("servings");
+ }
+ else if ( tagName == "PrpT" )
+ recipe->prepTime = QTime::fromString( el.attribute( "elapsed" ) );
+ else if ( tagName == "CatS" ) {
+ QDomNodeList categories = el.childNodes();
+ for ( unsigned j = 0; j < categories.count(); j++ ) {
+ QDomElement c = categories.item( j ).toElement();
+ if ( c.tagName() == "CatT" ) {
+ if ( c.text().length() > 0 ) {
+ Element cat( c.text().stripWhiteSpace() );
+ recipe->categoryList.append( cat );
+ }
+ }
+ }
+ }
+ else if ( tagName == "IngR" ) {
+ Ingredient new_ing( el.attribute( "name" ),
+ el.attribute( "qty" ).toDouble(),
+ Unit( el.attribute( "unit" ), el.attribute( "qty" ).toDouble() ) );
+ if ( el.hasChildNodes() ) {
+ QDomNodeList iChilds = el.childNodes();
+ for ( unsigned j = 0; j < iChilds.count(); j++ ) {
+ QDomElement iChild = iChilds.item( j ).toElement();
+ if ( iChild.tagName() == "IPrp" )
+ new_ing.prepMethodList.append( Element(iChild.text().stripWhiteSpace()) );
+ else if ( iChild.tagName() == "INtI" )
+ ; // TODO: What does it mean?... ingredient nutrient info?
+ }
+ }
+ recipe->ingList.append( new_ing );
+ }
+ else if ( tagName == "DirS" ) {
+ QStringList directions;
+ QDomNodeList dirs = el.childNodes();
+ for ( unsigned j = 0; j < dirs.count(); j++ ) {
+ QDomElement dir = dirs.item( j ).toElement();
+ if ( dir.tagName() == "DirT" )
+ directions.append( dir.text().stripWhiteSpace() );
+ }
+ QString directionsText;
+
+ // TODO This is copied from RecipeML, maybe a QStringList
+ // for directions in Recipe instead?
+ if ( directions.count() > 1 ) {
+ for ( unsigned i = 1; i <= directions.count(); i++ ) {
+ if ( i != 1 ) {
+ directionsText += "\n\n";
+ }
+
+ QString sWith = QString( "%1. " ).arg( i );
+ QString text = directions[ i - 1 ];
+ if ( !text.stripWhiteSpace().startsWith( sWith ) )
+ directionsText += sWith;
+ directionsText += text;
+ }
+ }
+ else
+ directionsText = directions[ 0 ];
+
+ recipe->instructions = directionsText;
+ }
+ else if ( tagName == "SrvI" ) {
+ // Don't know what to do with it, for now add it to directions
+ // btw lets hope this is read after the directions
+ recipe->instructions += "\n\n" + el.text().stripWhiteSpace();
+ }
+ else if ( tagName == "Note" ) {
+ // Don't know what to do with it, for now add it to directions
+ // btw lets hope this is read after the directions
+ recipe->instructions += "\n\n" + el.text().stripWhiteSpace();
+ }
+ else if ( tagName == "Nutr" ) {
+ //example: <Nutr>Per Serving (excluding unknown items): 51 Calories; 6g Fat (99.5% calories from fat); trace Protein; trace Carbohydrate; 0g Dietary Fiber; 16mg Cholesterol; 137mg Sodium. Exchanges: 1 Fat.</Nutr>
+ // Don't know what to do with it, for now add it to directions
+ // btw lets hope this is read after the directions
+ recipe->instructions += "\n\n" + el.text().stripWhiteSpace();
+ }
+ /* tags to check for (example follows:
+ <Srce>SARA&apos;S SECRETS with Sara Moulton - (Show # SS-1B43) - from the TV FOOD NETWORK</Srce>
+ <AltS label="Formatted for MC7" source="07-11-2003 by Joe Comiskey - Mad&apos;s Recipe Emporium"/>
+ */
+ // TODO Have i missed some tag?
+ }
+}
+
diff --git a/krecipes/src/importers/mx2importer.h b/krecipes/src/importers/mx2importer.h
new file mode 100644
index 0000000..22867d0
--- /dev/null
+++ b/krecipes/src/importers/mx2importer.h
@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2003 Richard Lrkng
+Copyright (C) 2003 Jason Kivlighn
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA
+*/
+
+#ifndef MX2IMPORTER_H
+#define MX2IMPORTER_H
+
+#include "baseimporter.h"
+
+#include <qdom.h>
+
+class Recipe;
+
+/** Class to import Mastercook's MX2 file format. This is an XML-based file
+ * format used since version 5 of Mastercook.
+ * @author Jason Kivlighn
+ */
+class MX2Importer : public BaseImporter
+{
+public:
+ MX2Importer();
+ virtual ~MX2Importer();
+
+protected:
+ void parseFile( const QString& filename );
+
+private:
+ void readRecipe( const QDomNodeList& l, Recipe* );
+} ;
+
+#endif //MX2IMPORTER_H
diff --git a/krecipes/src/importers/mxpimporter.cpp b/krecipes/src/importers/mxpimporter.cpp
new file mode 100644
index 0000000..201098d
--- /dev/null
+++ b/krecipes/src/importers/mxpimporter.cpp
@@ -0,0 +1,382 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "mxpimporter.h"
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qstringlist.h>
+#include <qdatetime.h>
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "datablocks/mixednumber.h"
+#include "datablocks/recipe.h"
+
+MXPImporter::MXPImporter() : BaseImporter()
+{}
+
+void MXPImporter::parseFile( const QString &file )
+{
+ QFile input( file );
+
+ if ( input.open( IO_ReadOnly ) ) {
+ QTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ QString line;
+ while ( !stream.atEnd() ) {
+ line = stream.readLine().stripWhiteSpace();
+
+ if ( line.simplifyWhiteSpace().contains( "Exported from MasterCook" ) ) {
+ importMXP( stream );
+ }
+ else if ( line == "{ Exported from MasterCook Mac }" ) {
+ importMac( stream );
+ }
+ else if ( line == "@@@@@" ) {
+ importGeneric( stream );
+ }
+
+ stream.skipWhiteSpace();
+ }
+
+ if ( fileRecipeCount() == 0 )
+ addWarningMsg( i18n( "No recipes found in this file." ) );
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+MXPImporter::~MXPImporter()
+{}
+
+void MXPImporter::importMXP( QTextStream &stream )
+{
+ Recipe recipe;
+
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ //kdDebug()<<"Found recipe MXP format: * Exported from MasterCook *"<<endl;
+ QString current;
+
+ // title
+ stream.skipWhiteSpace();
+ recipe.title = stream.readLine().stripWhiteSpace();
+ //kdDebug()<<"Found title: "<<m_title<<endl;
+
+ //author
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "recipe by" ) {
+ Element new_author( current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace() );
+ recipe.authorList.append( new_author );
+ //kdDebug()<<"Found author: "<<new_author.name<<endl;
+ }
+ else {
+ addWarningMsg( QString( i18n( "While loading recipe \"%1\" "
+ "the field \"Recipe By:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+ }
+
+ //servings
+ stream.skipWhiteSpace();
+ current = stream.readLine().stripWhiteSpace();
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "serving size" ) {
+ //allows serving size to be loaded even if preparation time is missing
+ int end_index;
+ if ( current.contains( "preparation time", FALSE ) )
+ end_index = current.find( "preparation time", 0, FALSE ) - 15;
+ else
+ end_index = current.length();
+
+ recipe.yield.amount = current.mid( current.find( ":" ) + 1, end_index ).stripWhiteSpace().toInt();
+ recipe.yield.type = i18n("servings");
+ //kdDebug()<<"Found serving size: "<<recipe.yield.amount<<endl;
+ }
+ else {
+ addWarningMsg( QString( i18n( "While loading recipe \"%1\" "
+ "the field \"Serving Size:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+ }
+
+ if ( current.contains( "preparation time", FALSE ) ) {
+ QString prep_time = current.mid( current.find( ":", current.find( "preparation time", 0, FALSE ) ) + 1,
+ current.length() ).stripWhiteSpace();
+ recipe.prepTime = QTime( prep_time.section( ':', 0, 0 ).toInt(), prep_time.section( ':', 1, 1 ).toInt() );
+ kdDebug() << "Found preparation time: " << prep_time << endl;
+ }
+ else {
+ addWarningMsg( QString( i18n( "While loading recipe \"%1\" "
+ "the field \"Preparation Time:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+ }
+
+ loadCategories( stream, recipe );
+ loadIngredients( stream, recipe );
+ loadInstructions( stream, recipe );
+ loadOptionalFields( stream, recipe );
+
+ add
+ ( recipe );
+
+ if ( !stream.atEnd() ) {
+ importMXP( stream );
+ return ;
+ }
+}
+
+void MXPImporter::loadCategories( QTextStream &stream, Recipe &recipe )
+{
+ //====================categories====================//
+ stream.skipWhiteSpace();
+ QString current = stream.readLine().stripWhiteSpace();
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "categories" ) {
+ QString tmp_str = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+
+ while ( current.stripWhiteSpace() != "Amount Measure Ingredient -- Preparation Method" && !stream.atEnd() ) {
+ if ( !tmp_str.isEmpty() ) {
+ QStringList categories = QStringList::split( " ", tmp_str );
+ for ( QStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat( ( *it ).stripWhiteSpace() );
+ recipe.categoryList.append( new_cat );
+
+ //kdDebug()<<"Found category: "<<new_cat.name<<endl;
+ }
+ }
+
+ current = stream.readLine();
+ tmp_str = current;
+ }
+ //else
+ // kdDebug()<<"No categories found."<<endl;
+ }
+ else {
+ addWarningMsg( QString( i18n( "While loading recipe \"%1\" "
+ "the field \"Categories:\" is either missing or could not be detected." ) ).arg( recipe.title ) );
+
+ //the ingredient loaded will expect the last thing to have been read to be this header line
+ while ( current.stripWhiteSpace() != "Amount Measure Ingredient -- Preparation Method" && !stream.atEnd() )
+ current = stream.readLine();
+ }
+}
+
+void MXPImporter::loadIngredients( QTextStream &stream, Recipe &recipe )
+{
+ //============ingredients=================//
+ stream.skipWhiteSpace();
+ ( void ) stream.readLine();
+ QString current = stream.readLine();
+ if ( !current.contains( "NONE" ) && !current.isEmpty() ) {
+ while ( !current.isEmpty() && !stream.atEnd() ) {
+ Ingredient new_ingredient;
+
+ //amount
+ QString amount_str = current.mid( 0, 9 ).simplifyWhiteSpace();
+ if ( !amount_str.isEmpty() ) // case of amount_str.isEmpty() correctly handled by class default
+ {
+ bool ok;
+ MixedNumber amount( MixedNumber::fromString( amount_str, &ok ) );
+ if ( !ok )
+ {
+ addWarningMsg( QString( i18n( "While loading recipe \"%1\" Invalid amount \"%2\" in the line \"%3\"" ) ).arg( recipe.title ).arg( amount_str ).arg( current.stripWhiteSpace() ) );
+ current = stream.readLine();
+ continue;
+ }
+ new_ingredient.amount = amount.toDouble();
+ }
+
+ //units
+ QString units( current.mid( 9, 13 ) );
+ new_ingredient.units = Unit( units.simplifyWhiteSpace(), new_ingredient.amount );
+
+ //name
+ int dash_index = current.find( "--" );
+
+ int length;
+ if ( dash_index == -1 || dash_index == 24 ) //ignore a dash in the first position (index 24)
+ length = current.length();
+ else
+ length = dash_index - 22;
+
+ QString ingredient_name( current.mid( 22, length ) );
+ new_ingredient.name = ingredient_name.stripWhiteSpace();
+
+ //prep method
+ if ( dash_index != -1 && dash_index != 24 ) //ignore a dash in the first position (index 24)
+ new_ingredient.prepMethodList.append( Element(current.mid( dash_index + 2, current.length() ).stripWhiteSpace()) );
+
+ recipe.ingList.append( new_ingredient );
+ //kdDebug()<<"Found ingredient: amount="<<new_ingredient.amount
+ // <<", unit:"<<new_ingredient.units
+ // <<", name:"<<new_ingredient.name
+ // <<", prep_method:"<<prep_method<<endl;
+
+ current = stream.readLine();
+ }
+ }
+ //else
+ // kdDebug()<<"No ingredients found."<<endl;
+}
+
+void MXPImporter::loadInstructions( QTextStream &stream, Recipe &recipe )
+{
+ //==========================instructions ( along with other optional fields... mxp format doesn't define end of ingredients and start of other fields )==============//
+ stream.skipWhiteSpace();
+ QString current = stream.readLine().stripWhiteSpace();
+ while ( !current.contains( "- - - -" ) && !stream.atEnd() ) {
+ if ( current.stripWhiteSpace() == "Source:" ) {
+ Element new_author( getNextQuotedString( stream ) );
+ recipe.authorList.append( new_author );
+ //kdDebug()<<"Found source: "<<new_author.name<<" (adding as author)"<<endl;
+ }
+ else if ( current.stripWhiteSpace() == "Description:" ) {
+ QString description = getNextQuotedString( stream );
+ //kdDebug()<<"Found description: "<<m_description<<" (adding to end of instructions)"<<endl;
+ recipe.instructions += "\n\nDescription: " + description;
+ }
+ else if ( current.stripWhiteSpace() == "S(Internet Address):" ) {
+ QString internet = getNextQuotedString( stream );
+ //kdDebug()<<"Found internet address: "<<m_internet<<" (adding to end of instructions)"<<endl;
+ recipe.instructions += "\n\nInternet address: " + internet;
+ }
+ else if ( current.stripWhiteSpace() == "Yield:" ) {
+ recipe.yield.amount = getNextQuotedString( stream ).stripWhiteSpace().toInt();
+ recipe.yield.type = i18n("servings");
+ //kdDebug()<<"Found yield: "<<recipe.yield.amount<<" (adding as servings)"<<endl;
+ }
+ else if ( current.stripWhiteSpace() == "T(Cook Time):" ) {
+ ( void ) getNextQuotedString( stream ); //this would be prep time, but we don't use prep time at the moment
+ //kdDebug()<<"Found cook time: "<<m_prep_time<<" (adding as prep time)"<<endl;
+ }
+ else if ( current.stripWhiteSpace() == "Cuisine:" ) {
+ Element new_cat( getNextQuotedString( stream ) );
+ recipe.categoryList.append( new_cat );
+ //kdDebug()<<"Found cuisine (adding as category): "<<new_cat.name<<endl;
+ }
+ else
+ recipe.instructions += current + "\n";
+
+ current = stream.readLine().stripWhiteSpace();
+ }
+ recipe.instructions = recipe.instructions.stripWhiteSpace();
+ //kdDebug()<<"Found instructions: "<<m_instructions<<endl;
+}
+
+void MXPImporter::loadOptionalFields( QTextStream &stream, Recipe &recipe )
+{
+ //=================after here, fields are optional=========================//
+ stream.skipWhiteSpace();
+ QString current = stream.readLine().stripWhiteSpace();
+
+ QString notes;
+
+ //Note: we simplifyWhiteSpace() because some versions of MasterCook have "Exported from MasterCook" and others have "Exported from MasterCook".
+ // This also could work around a typo or such.
+ while ( !current.simplifyWhiteSpace().contains( "Exported from MasterCook" ) && !stream.atEnd() ) {
+ //suggested wine
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "suggested wine" ) {
+ QString wine = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found suggested wine: "<<m_wine<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nSuggested wine: " + wine;
+ }
+ //Nutr. Assoc.
+ if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "nutr. assoc." ) {
+ QString nutr_assoc = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found nutrient association: "<<nutr_assoc<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nNutrient Association: " + nutr_assoc;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "per serving (excluding unknown items)" ) { //per serving... maybe we can do something with this info later
+ QString per_serving_info = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found per serving (excluding unknown items): "<<per_serving_info<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nPer Serving (excluding unknown items): " + per_serving_info;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "per serving" ) { //per serving... maybe we can do something with this info later
+ QString per_serving_info = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found per serving: "<<per_serving_info<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nPer Serving: " + per_serving_info;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "food exchanges" ) { //food exchanges... maybe we can do something with this info later
+ QString food_exchange_info = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found food exchanges: "<<food_exchange_info<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nFood Exchanges: " + food_exchange_info;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "serving ideas" ) { //serving ideas
+ QString serving_ideas = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ //kdDebug()<<"Found serving ideas: "<<m_serving_ideas<<" (adding to end of instructions)"<<endl;
+
+ recipe.instructions += "\n\nServing ideas: " + serving_ideas;
+ }
+ else if ( current.mid( 0, current.find( ":" ) ).simplifyWhiteSpace().lower() == "notes" ) //notes
+ notes = current.mid( current.find( ":" ) + 1, current.length() ).stripWhiteSpace();
+ else if ( !current.isEmpty() && current != "_____" ) //if it doesn't belong to any other field, assume it a part of a multi-line notes field
+ notes += "\n" + current;
+
+ current = stream.readLine().stripWhiteSpace();
+ }
+
+ /*possible fields to implement later:
+
+ Nutr. Assoc. : 0 0 0 0 0
+
+ Ratings : Cholesterol Rating 5 Complete Meal 3
+ Cost 3 Depth 3
+ Difficulty 2 Fanciness 7
+ Fat Content 5 Good For Crowds 10
+ Intensity 5 Intricacy 2
+ Kid Appeal 3 Looks 5
+ Portability 3 Richness 7
+ Serving Temperature 8 Spicy Hotness 2
+ Tartness 7
+
+ */
+ if ( !notes.isNull() ) {
+ //kdDebug()<<QString("Found notes: %s (adding to end of instructions)").arg(m_notes)<<endl;
+ recipe.instructions += "\n\nNotes: " + notes.stripWhiteSpace();
+ }
+}
+
+void MXPImporter::importGeneric( QTextStream & /*stream*/ )
+{
+ setErrorMsg( i18n( "MasterCook's Generic Export format is currently not supported. Please write to jkivlighn@gmail.com to request support for this format." ) );
+ //not even sure it this is worth writing... its rather obsolete
+}
+
+void MXPImporter::importMac( QTextStream & /*stream*/ )
+{
+ setErrorMsg( i18n( "MasterCook Mac's Export format is currently not supported. Please write to jkivlighn@gmail.com to request support for this format." ) );
+ //not even sure it this is worth writing... its rather obsolete
+}
+
+QString MXPImporter::getNextQuotedString( QTextStream &stream )
+{
+ stream.skipWhiteSpace();
+ QString current = stream.readLine().stripWhiteSpace();
+ QString return_str;
+
+ if ( current.left( 1 ) == "\"" )
+ return_str = current.mid( 1, current.length() - 1 );
+ else
+ return current;
+
+ while ( current.right( 1 ) != "\"" && !stream.atEnd() ) {
+ current = stream.readLine().stripWhiteSpace();
+ return_str += "\n" + current;
+ }
+
+ //take off quote at end
+ return_str = return_str.mid( 0, return_str.length() - 1 );
+
+ return return_str.stripWhiteSpace();
+}
diff --git a/krecipes/src/importers/mxpimporter.h b/krecipes/src/importers/mxpimporter.h
new file mode 100644
index 0000000..d45a091
--- /dev/null
+++ b/krecipes/src/importers/mxpimporter.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef MXPIMPORTER_H
+#define MXPIMPORTER_H
+
+#include <qstring.h>
+
+#include "baseimporter.h"
+
+/** Class to import MasterCook's MXP (MasterCook Export) file format.
+ * This is a human-readable format used in Mastercook up until version 4.
+ * @author Jason Kivlighn
+ */
+class MXPImporter : public BaseImporter
+{
+public:
+ MXPImporter();
+ virtual ~MXPImporter();
+
+protected:
+ void parseFile( const QString& filename );
+
+private:
+ void importMXP( QTextStream &stream );
+
+ void loadCategories( QTextStream &stream, Recipe &recipe );
+ void loadIngredients( QTextStream &stream, Recipe &recipe );
+ void loadInstructions( QTextStream &stream, Recipe &recipe );
+ void loadOptionalFields( QTextStream &stream, Recipe &recipe );
+
+ void importMac( QTextStream &stream );
+ void importGeneric( QTextStream &stream );
+
+ QString getNextQuotedString( QTextStream &stream );
+};
+
+#endif //MXPIMPORTER_H
diff --git a/krecipes/src/importers/nycgenericimporter.cpp b/krecipes/src/importers/nycgenericimporter.cpp
new file mode 100644
index 0000000..4105eea
--- /dev/null
+++ b/krecipes/src/importers/nycgenericimporter.cpp
@@ -0,0 +1,196 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "nycgenericimporter.h"
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qstringlist.h>
+#include <qregexp.h>
+
+#include "datablocks/mixednumber.h"
+#include "datablocks/recipe.h"
+
+NYCGenericImporter::NYCGenericImporter() : BaseImporter()
+{}
+
+void NYCGenericImporter::parseFile( const QString &file )
+{
+ first = true;
+
+ m_recipe.empty();
+
+ QFile input( file );
+ if ( input.open( IO_ReadOnly ) ) {
+ QTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ if ( !stream.atEnd() && stream.readLine().startsWith( "@@@@@" ) )
+ importNYCGeneric( stream );
+ else {
+ setErrorMsg( i18n( "File does not appear to be a valid NYC export." ) );
+ return ;
+ }
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+NYCGenericImporter::~NYCGenericImporter()
+{}
+
+void NYCGenericImporter::importNYCGeneric( QTextStream &stream )
+{
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ QString current;
+
+ stream.skipWhiteSpace();
+
+ //title
+ while ( !( current = stream.readLine() ).isEmpty() && !stream.atEnd() )
+ m_recipe.title = current;
+
+ //categories
+ while ( !( current = stream.readLine() ).isEmpty() && !stream.atEnd() ) {
+ if ( current[ 0 ].isNumber() ) {
+ loadIngredientLine( current );
+ break;
+ } //oops, this is really an ingredient line (there was no category line)
+
+ QStringList categories = QStringList::split( ',', current );
+
+ if ( categories.count() > 0 && categories[ 0 ].upper() == "none" ) //there are no categories
+ break;
+
+ for ( QStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat( QString( *it ).stripWhiteSpace() );
+ kdDebug() << "Found category: " << new_cat.name << endl;
+ m_recipe.categoryList.append( new_cat );
+ }
+ }
+
+ //ingredients
+ while ( !( current = stream.readLine() ).isEmpty() && !stream.atEnd() )
+ loadIngredientLine( current );
+
+ //everything else is the instructions with optional "contributor", "prep time" and "yield"
+ bool found_next;
+ while ( !( found_next = ( current = stream.readLine() ).startsWith( "@@@@@" ) ) && !stream.atEnd() ) {
+ if ( current.startsWith( "Contributor:" ) ) {
+ Element new_author( current.mid( current.find( ':' ) + 1, current.length() ).stripWhiteSpace() );
+ kdDebug() << "Found author: " << new_author.name << endl;
+ m_recipe.authorList.append( new_author );
+ }
+ else if ( current.startsWith( "Preparation Time:" ) ) {
+ m_recipe.prepTime = QTime::fromString( current.mid( current.find( ':' ), current.length() ) );
+ }
+ else if ( current.startsWith( "Yield:" ) ) {
+ int colon_index = current.find( ':' );
+ int amount_type_sep_index = current.find(" ",colon_index+1);
+
+ m_recipe.yield.amount = current.mid( colon_index+2, amount_type_sep_index-colon_index ).toDouble();
+ m_recipe.yield.type = current.mid( amount_type_sep_index+3, current.length() );
+ }
+ else if ( current.startsWith( "NYC Nutrition Analysis (per serving or yield unit):" ) ) {
+ //m_recipe.instructions += current + "\n";
+ }
+ else if ( current.startsWith( "NYC Nutrilink:" ) ) {
+ //m_recipe.instructions += current + "\n";
+ }
+ else if ( !current.stripWhiteSpace().isEmpty() && !current.startsWith("** Exported from Now You're Cooking!") ) {
+ m_recipe.instructions += current + "\n";
+ }
+ }
+
+ m_recipe.instructions = m_recipe.instructions.stripWhiteSpace();
+ putDataInRecipe();
+
+ if ( found_next )
+ importNYCGeneric( stream );
+}
+
+void NYCGenericImporter::putDataInRecipe()
+{
+ //put it in the recipe list
+ add( m_recipe );
+
+ //reset for the next recipe
+ m_recipe.empty();
+}
+
+void NYCGenericImporter::loadIngredientLine( const QString &line )
+{
+ QString current = line;
+
+ if ( current.contains( "-----" ) ) {
+ current_header = current.stripWhiteSpace();
+ kdDebug() << "Found ingredient header: " << current_header << endl;
+ return ;
+ }
+
+ MixedNumber amount( 0, 0, 1 );
+ QString unit;
+ QString name;
+ QString prep;
+
+ QStringList ingredient_line = QStringList::split( ' ', current );
+
+ bool found_amount = false;
+
+ if ( !ingredient_line.empty() ) //probably an unnecessary check... but to be safe
+ {
+ bool ok;
+ MixedNumber test_amount = MixedNumber::fromString( ingredient_line[ 0 ], &ok );
+ if ( ok )
+ {
+ amount = amount + test_amount;
+ ingredient_line.pop_front();
+ found_amount = true;
+ }
+ }
+ if ( !ingredient_line.empty() ) //probably an unnecessary check... but to be safe
+ {
+ bool ok;
+ MixedNumber test_amount = MixedNumber::fromString( ingredient_line[ 0 ], &ok );
+ if ( ok )
+ {
+ amount = amount + test_amount;
+ ingredient_line.pop_front();
+ found_amount = true;
+ }
+ }
+
+ if ( found_amount ) {
+ unit = ingredient_line[ 0 ];
+ ingredient_line.pop_front();
+ }
+
+ //now join each separate part of ingredient (name, unit, amount)
+ name = ingredient_line.join( " " );
+
+ int prep_sep_index = name.find( QRegExp( "(--|,;;)" ) );
+ if ( prep_sep_index == -1 )
+ prep_sep_index = name.length();
+
+ name = name.left( prep_sep_index ).stripWhiteSpace();
+ prep = name.mid( prep_sep_index+1, name.length() ).stripWhiteSpace();
+
+ Ingredient new_ingredient( name, amount.toDouble(), Unit( unit, amount.toDouble() ) );
+ new_ingredient.group = current_header;
+ new_ingredient.prepMethodList = ElementList::split(",",prep);
+ m_recipe.ingList.append( new_ingredient );
+
+}
+
diff --git a/krecipes/src/importers/nycgenericimporter.h b/krecipes/src/importers/nycgenericimporter.h
new file mode 100644
index 0000000..f0ac1c3
--- /dev/null
+++ b/krecipes/src/importers/nycgenericimporter.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef NYCGENERICIMPORTER_H
+#define NYCGENERICIMPORTER_H
+
+#include <qstring.h>
+#include <qdatetime.h>
+
+#include "baseimporter.h"
+#include "datablocks/ingredientlist.h"
+#include "datablocks/elementlist.h"
+
+/** Class to import The NYC (Now You're Cooking) Generic Export file format.
+ * @author Jason Kivlighn
+ */
+class NYCGenericImporter : public BaseImporter
+{
+public:
+ NYCGenericImporter();
+ ~NYCGenericImporter();
+
+protected:
+ void parseFile( const QString& filename );
+
+private:
+ void importNYCGeneric( QTextStream &stream );
+ void putDataInRecipe();
+ void loadIngredientLine( const QString & );
+
+ Recipe m_recipe;
+ QString current_header;
+
+ bool first;
+};
+
+#endif //NYCGENERICIMPORTER_H
diff --git a/krecipes/src/importers/recipemlimporter.cpp b/krecipes/src/importers/recipemlimporter.cpp
new file mode 100644
index 0000000..d32e352
--- /dev/null
+++ b/krecipes/src/importers/recipemlimporter.cpp
@@ -0,0 +1,376 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Richard Lrkng *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipemlimporter.h"
+
+#include <qfile.h>
+#include <qdatetime.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "datablocks/recipe.h"
+#include "datablocks/mixednumber.h"
+
+RecipeMLImporter::RecipeMLImporter() : BaseImporter()
+{}
+
+void RecipeMLImporter::parseFile( const QString& file )
+{
+ QFile input( file );
+ if ( input.open( IO_ReadOnly ) ) {
+ QDomDocument doc;
+ QString error;
+ int line;
+ int column;
+ if ( !doc.setContent( &input, &error, &line, &column ) ) {
+ setErrorMsg( QString( i18n( "\"%1\" at line %2, column %3. This may not be a RecipeML file." ) ).arg( error ).arg( line ).arg( column ) );
+ return ;
+ }
+
+ QDomElement recipeml = doc.documentElement();
+
+ if ( recipeml.tagName() != "recipeml" ) {
+ setErrorMsg( i18n( "This file does not appear to be a valid RecipeML archive." ) );
+ return ;
+ }
+
+ QDomNodeList l = recipeml.childNodes();
+
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+
+ if ( tagName == "meta" )
+ continue;
+ else if ( tagName == "recipe" )
+ readRecipemlRecipe( el );
+ else if ( tagName == "menu" )
+ readRecipemlMenu( el );
+ else
+ kdDebug() << "Unknown tag within <recipeml>: " << tagName << endl;
+ }
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+RecipeMLImporter::~RecipeMLImporter()
+{}
+
+void RecipeMLImporter::readRecipemlRecipe( const QDomElement& recipe_element )
+{
+ recipe.empty();
+
+ QDomNodeList l = recipe_element.childNodes();
+
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+
+ if ( tagName == "head" )
+ readRecipemlHead( el );
+ else if ( tagName == "ingredients" )
+ readRecipemlIngs( el );
+ else if ( tagName == "directions" )
+ readRecipemlDirections( el );
+ else if ( tagName == "description" ) {} //TODO: what do we do with this?
+ else if ( tagName == "equipment" ) {} //TODO: what do we do with this?
+ else if ( tagName == "nutrition" ) {} //TODO: what do we do with this?
+ else if ( tagName == "diet-exchanges" ) {} //TODO: what do we do with this?
+ else
+ kdDebug() << "Unknown tag within <recipe>: " << el.tagName() << endl;
+ }
+
+ add
+ ( recipe );
+}
+
+void RecipeMLImporter::readRecipemlHead( const QDomElement& head )
+{
+ QDomNodeList l = head.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+
+ if ( tagName == "title" )
+ recipe.title = el.text().stripWhiteSpace();
+ else if ( tagName == "subtitle" )
+ recipe.title += ": " + el.text().stripWhiteSpace();
+ else if ( tagName == "version" ) {} //TODO: what do we do with this?
+ else if ( tagName == "source" )
+ readRecipemlSrcItems( el );
+ else if ( tagName == "categories" ) {
+ QDomNodeList categories = el.childNodes();
+ for ( unsigned j = 0; j < categories.count(); j++ ) {
+ QDomElement c = categories.item( j ).toElement();
+ if ( c.tagName() == "cat" ) {
+ recipe.categoryList.append( Element( c.text() ) );
+ }
+ }
+ }
+ else if ( tagName == "description" )
+ recipe.instructions += "\n\nDescription: " + el.text().stripWhiteSpace();
+ else if ( tagName == "preptime" )
+ readRecipemlPreptime( el );
+ else if ( tagName == "yield" ) {
+ QDomNodeList yieldChildren = el.childNodes();
+ for ( unsigned j = 0; j < yieldChildren.count(); j++ ) {
+ QDomElement y = yieldChildren.item( j ).toElement();
+ QString tagName = y.tagName();
+ if ( tagName == "range" )
+ readRecipemlRange( y, recipe.yield.amount, recipe.yield.amount_offset );
+ else if ( tagName == "unit" )
+ recipe.yield.type = y.text();
+ else
+ kdDebug() << "Unknown tag within <yield>: " << y.tagName() << endl;
+ }
+ }
+ else
+ kdDebug() << "Unknown tag within <head>: " << el.tagName() << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlIngs( const QDomElement& ings )
+{
+ QDomNodeList l = ings.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+
+ if ( tagName == "ing" )
+ readRecipemlIng( el );
+ else if ( tagName == "ing-div" ) //NOTE: this can have the "type" attribute
+ {
+ QString header;
+ QDomNodeList ingDiv = el.childNodes();
+ for ( unsigned j = 0; j < ingDiv.count(); j++ )
+ {
+ QDomElement cEl = ingDiv.item( j ).toElement();
+ if ( cEl.tagName() == "title" )
+ header = cEl.text().stripWhiteSpace();
+ else if ( cEl.tagName() == "description" ) {} //TODO: what do we do with this?
+ else if ( cEl.tagName() == "ing" )
+ readRecipemlIng( cEl, 0, header );
+ else if ( tagName == "note" ) {} //TODO: what do we do with this?
+ else
+ kdDebug() << "Unknown tag within <ing-div>: " << cEl.tagName() << endl;
+ }
+ }
+ else if ( tagName == "note" ) {} //TODO: what do we do with this?
+ else
+ kdDebug() << "Unknown tag within <ingredients>: " << el.tagName() << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlIng( const QDomElement& ing, Ingredient *ing_parent, const QString &header )
+{
+ Ingredient new_ing;
+
+ QDomNodeList ingChilds = ing.childNodes();
+
+ QString name, unit, size, prep_method;
+ Ingredient quantity;
+ quantity.amount = 1;// default quantity assumed by RecipeML DTD
+
+ for ( unsigned j = 0; j < ingChilds.count(); j++ ) {
+ QDomElement ingChild = ingChilds.item( j ).toElement();
+ QString tagName = ingChild.tagName();
+
+ if ( tagName == "amt" ) {
+ QDomNodeList amtChilds = ingChild.childNodes();
+
+ for ( unsigned k = 0; k < amtChilds.count(); k++ ) {
+ QDomElement amtChild = amtChilds.item( k ).toElement();
+
+ if ( amtChild.tagName() == "qty" )
+ readRecipemlQty( amtChild, quantity );
+ else if ( amtChild.tagName() == "size" )
+ size = amtChild.text().stripWhiteSpace();
+ else if ( amtChild.tagName() == "unit" )
+ unit = amtChild.text().stripWhiteSpace();
+ else
+ kdDebug() << "Unknown tag within <amt>: " << amtChild.tagName() << endl;
+ }
+ }
+ else if ( tagName == "item" ) {
+ name = ingChild.text().stripWhiteSpace();
+ if ( ing.attribute( "optional", "no" ) == "yes" )
+ prep_method = "(optional)";
+ }
+ else if ( tagName == "prep" ) { //FIXME: this overwrite the optional attribute
+ prep_method = ingChild.text().stripWhiteSpace();
+ }
+ else if ( tagName == "alt-ing" )
+ readRecipemlIng( ingChild, &new_ing, header );
+ else
+ kdDebug() << "Unknown tag within <ing>: " << ingChild.tagName() << endl;
+ }
+
+ if ( !size.isEmpty() )
+ unit.prepend( size + " " );
+
+ new_ing.name = name;
+ new_ing.units = Unit( unit, quantity.amount+quantity.amount_offset );
+ new_ing.amount = quantity.amount;
+ new_ing.amount_offset = quantity.amount_offset;
+ new_ing.group = header;
+ new_ing.prepMethodList = ElementList::split(",",prep_method);
+
+ if ( !ing_parent )
+ recipe.ingList.append(new_ing);
+ else
+ ing_parent->substitutes.append( new_ing );
+}
+
+void RecipeMLImporter::readRecipemlDirections( const QDomElement& dirs )
+{
+ QDomNodeList l = dirs.childNodes();
+
+ QStringList directions;
+
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+
+ if ( el.tagName() == "step" )
+ directions.append( el.text().stripWhiteSpace() );
+ else
+ kdDebug() << "Unknown tag within <directions>: " << el.tagName() << endl;
+ }
+
+ QString directionsText;
+
+ if ( directions.count() > 1 ) {
+ for ( unsigned i = 1; i <= directions.count(); i++ ) {
+ if ( i != 1 ) {
+ directionsText += "\n\n";
+ }
+
+ QString sWith = QString( "%1. " ).arg( i );
+ QString text = directions[ i - 1 ];
+ if ( !text.stripWhiteSpace().startsWith( sWith ) )
+ directionsText += sWith;
+ directionsText += text;
+ }
+ }
+ else
+ directionsText = directions[ 0 ];
+
+ recipe.instructions = directionsText;
+}
+
+void RecipeMLImporter::readRecipemlMenu( const QDomElement& menu_el )
+{
+ QDomNodeList l = menu_el.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+
+ if ( tagName == "head" )
+ readRecipemlHead( el );
+ else if ( tagName == "description" ) {} //TODO: what do we do with this?
+ else if ( tagName == "recipe" )
+ readRecipemlRecipe( el );
+ else
+ kdDebug() << "Unknown tag within <menu>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlSrcItems( const QDomElement& sources )
+{
+ QDomNodeList l = sources.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement srcitem = l.item( i ).toElement();
+ QString tagName = srcitem.tagName();
+
+ if ( tagName == "srcitem" )
+ recipe.authorList.append( Element( srcitem.text().stripWhiteSpace() ) );
+ else
+ kdDebug() << "Unknown tag within <source>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlPreptime( const QDomElement &preptime )
+{
+ QDomNodeList l = preptime.childNodes();
+ for ( unsigned i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+
+ if ( tagName == "time" ) {
+ int qty = 0;
+ QString timeunit;
+
+ QDomNodeList time_l = el.childNodes();
+ for ( unsigned i = 0 ; i < time_l.count(); i++ ) {
+ QDomElement time_el = time_l.item( i ).toElement();
+ QString time_tagName = time_el.tagName();
+
+ if ( time_tagName == "qty" )
+ qty = time_el.text().toInt();
+ else if ( time_tagName == "timeunit" )
+ timeunit = time_el.text();
+ else
+ kdDebug() << "Unknown tag within <time>: " << time_tagName << endl;
+ }
+
+ int minutes = 0;
+ int hours = 0;
+ if ( timeunit == "minutes" )
+ minutes = qty;
+ else if ( timeunit == "hours" )
+ hours = qty;
+ else
+ kdDebug() << "Unknown timeunit: " << timeunit << endl;
+
+ recipe.prepTime = QTime( hours + minutes / 60, minutes % 60 );
+ }
+ else
+ kdDebug() << "Unknown tag within <preptime>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlQty( const QDomElement &qty, Ingredient &ing )
+{
+ QDomNodeList qtyChilds = qty.childNodes();
+
+ for ( unsigned i = 0; i < qtyChilds.count(); i++ ) {
+ QDomElement qtyChild = qtyChilds.item( i ).toElement();
+ QString tagName = qtyChild.tagName();
+ if ( tagName == "range" )
+ readRecipemlRange( qtyChild, ing.amount, ing.amount_offset );
+ else if ( tagName.isEmpty() )
+ ing.amount = MixedNumber::fromString( qty.text() ).toDouble();
+ else
+ kdDebug() << "Unknown tag within <qty>: " << tagName << endl;
+ }
+}
+
+void RecipeMLImporter::readRecipemlRange( const QDomElement& range, double &amount, double &amount_offset )
+{
+ QDomNodeList rangeChilds = range.childNodes();
+ double q1 = 1, q2 = 0; // default quantity assumed by RecipeML DTD
+ for ( unsigned j = 0; j < rangeChilds.count(); j++ ) {
+ QDomElement rangeChild = rangeChilds.item( j ).toElement();
+ QString subTagName = rangeChild.tagName();
+ if ( subTagName == "q1" )
+ q1 = MixedNumber::fromString( rangeChild.text() ).toDouble();
+ else if ( subTagName == "q2" )
+ q2 = MixedNumber::fromString( rangeChild.text() ).toDouble();
+ else
+ kdDebug() << "Unknown tag within <range>: " << subTagName << endl;
+ }
+
+ amount = q1;
+ amount_offset = q2-q1;
+}
diff --git a/krecipes/src/importers/recipemlimporter.h b/krecipes/src/importers/recipemlimporter.h
new file mode 100644
index 0000000..b330a05
--- /dev/null
+++ b/krecipes/src/importers/recipemlimporter.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Richard Lrkng *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+
+#ifndef RECIPEMLIMPORTER_H
+#define RECIPEMLIMPORTER_H
+
+#include "baseimporter.h"
+#include "datablocks/ingredient.h"
+#include "datablocks/recipe.h"
+
+#include <qdom.h>
+
+/** Class to import the RecipeML, XML-based file format.
+ * More info at http://www.formatdata.com/recipeml
+ *
+ * @author Jason Kivlighn
+ */
+class RecipeMLImporter : public BaseImporter
+{
+public:
+ RecipeMLImporter();
+ virtual ~RecipeMLImporter();
+
+protected:
+ void parseFile( const QString& filename );
+
+private:
+ void readRecipemlDirections( const QDomElement& dirs );
+ void readRecipemlHead( const QDomElement& head );
+ void readRecipemlIng( const QDomElement& ing1, Ingredient *ing2 = 0, const QString &header = QString::null );
+ void readRecipemlIngs( const QDomElement& ings );
+ void readRecipemlMenu( const QDomElement& menu );
+ void readRecipemlSrcItems( const QDomElement& sources );
+ void readRecipemlRecipe( const QDomElement& recipe );
+ void readRecipemlPreptime( const QDomElement &preptime );
+ void readRecipemlQty( const QDomElement &qty, Ingredient &ing );
+ void readRecipemlRange( const QDomElement& range1, double &range2, double &range_offset );
+
+ Recipe recipe;
+};
+
+#endif //RECIPEMLIMPORTER_H
diff --git a/krecipes/src/importers/rezkonvimporter.cpp b/krecipes/src/importers/rezkonvimporter.cpp
new file mode 100644
index 0000000..2f7f428
--- /dev/null
+++ b/krecipes/src/importers/rezkonvimporter.cpp
@@ -0,0 +1,291 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "rezkonvimporter.h"
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qregexp.h>
+#include <qtextstream.h>
+
+#include "datablocks/mixednumber.h"
+
+RezkonvImporter::RezkonvImporter() : BaseImporter()
+{}
+
+RezkonvImporter::~RezkonvImporter()
+{}
+
+void RezkonvImporter::parseFile( const QString &filename )
+{
+ QFile input( filename );
+
+ if ( input.open( IO_ReadOnly ) ) {
+ QTextStream stream( &input );
+ stream.skipWhiteSpace();
+
+ QString line;
+
+ while ( !stream.atEnd() ) {
+ line = stream.readLine();
+
+ if ( line.contains( QRegExp( "^=====.*REZKONV.*" ) ) ) {
+ QStringList raw_recipe;
+ while ( !( line = stream.readLine() ).contains( QRegExp( "^=====\\s*$" ) ) && !stream.atEnd() )
+ raw_recipe << line;
+
+ readRecipe( raw_recipe );
+ }
+ }
+
+ if ( fileRecipeCount() == 0 )
+ setErrorMsg( i18n( "No recipes found in this file." ) );
+ }
+ else
+ setErrorMsg( i18n( "Unable to open file." ) );
+}
+
+void RezkonvImporter::readRecipe( const QStringList &raw_recipe )
+{
+ kapp->processEvents(); //don't want the user to think its frozen... especially for files with thousands of recipes
+
+ Recipe recipe;
+
+ QStringList::const_iterator text_it = raw_recipe.begin();
+ m_end_it = raw_recipe.end();
+
+ //title (Titel)
+ text_it++;
+ recipe.title = ( *text_it ).mid( ( *text_it ).find( ":" ) + 1, ( *text_it ).length() ).stripWhiteSpace();
+ kdDebug() << "Found title: " << recipe.title << endl;
+
+ //categories (Kategorien):
+ text_it++;
+ QStringList categories = QStringList::split( ',', ( *text_it ).mid( ( *text_it ).find( ":" ) + 1, ( *text_it ).length() ) );
+ for ( QStringList::const_iterator it = categories.begin(); it != categories.end(); ++it ) {
+ Element new_cat;
+ new_cat.name = QString( *it ).stripWhiteSpace();
+ kdDebug() << "Found category: " << new_cat.name << endl;
+ recipe.categoryList.append( new_cat );
+ }
+
+ //yield (Menge)
+ text_it++;
+ //get the number between the ":" and the next space after it
+ QString yield_str = ( *text_it ).stripWhiteSpace();
+ yield_str.remove( QRegExp( "^Menge:\\s*" ) );
+ int sep_index = yield_str.find( ' ' );
+ if ( sep_index != -1 )
+ recipe.yield.type = yield_str.mid( sep_index+1 );
+ readRange( yield_str.mid( 0, sep_index ), recipe.yield.amount, recipe.yield.amount_offset );
+ kdDebug() << "Found yield: " << recipe.yield.amount << endl;
+
+ bool is_sub = false;
+ bool last_line_empty = false;
+ text_it++;
+ while ( text_it != raw_recipe.end() ) {
+ if ( ( *text_it ).isEmpty() ) {
+ last_line_empty = true;
+ text_it++;
+ continue;
+ }
+
+ if ( ( *text_it ).contains( QRegExp( "^=====.*=$" ) ) ) //is a header
+ {
+ if ( ( *text_it ).contains( "quelle", false ) )
+ {
+ loadReferences( text_it, recipe );
+ break; //reference lines are the last before the instructions
+ }
+ else
+ loadIngredientHeader( *text_it, recipe );
+ }
+
+ //if it has no more than two spaces followed by a non-digit
+ //then we'll assume it is a direction line
+ else if ( last_line_empty && ( *text_it ).contains( QRegExp( "^\\s{0,2}[^\\d\\s=]" ) ) )
+ break;
+ else
+ loadIngredient( *text_it, recipe, is_sub );
+
+ last_line_empty = false;
+ text_it++;
+ }
+
+ loadInstructions( text_it, recipe );
+
+ add
+ ( recipe );
+
+ current_header = QString::null;
+}
+
+void RezkonvImporter::loadIngredient( const QString &string, Recipe &recipe, bool &is_sub )
+{
+ Ingredient new_ingredient;
+ new_ingredient.amount = 0; //amount not required, so give default of 0
+
+ QRegExp cont_test( "^-{1,2}" );
+ if ( string.stripWhiteSpace().contains( cont_test ) ) {
+ QString name = string.stripWhiteSpace();
+ name.remove( cont_test );
+ kdDebug() << "Appending to last ingredient: " << name << endl;
+ if ( !recipe.ingList.isEmpty() ) //so it doesn't crash when the first ingredient appears to be a continuation of another
+ ( *recipe.ingList.at( recipe.ingList.count() - 1 ) ).name += " " + name;
+
+ return ;
+ }
+
+ //amount
+ if ( !string.mid( 0, 7 ).stripWhiteSpace().isEmpty() )
+ readRange( string.mid( 0, 7 ), new_ingredient.amount, new_ingredient.amount_offset );
+
+ //unit
+ QString unit_str = string.mid( 8, 9 ).stripWhiteSpace();
+ new_ingredient.units = Unit( unit_str, new_ingredient.amount );
+
+ //name and preparation method
+ new_ingredient.name = string.mid( 18, string.length() - 18 ).stripWhiteSpace();
+
+ //separate out the preparation method
+ QString name_and_prep = new_ingredient.name;
+ int separator_index = name_and_prep.find( "," );
+ if ( separator_index != -1 ) {
+ new_ingredient.name = name_and_prep.mid( 0, separator_index ).stripWhiteSpace();
+ new_ingredient.prepMethodList = ElementList::split(",",name_and_prep.mid( separator_index + 1, name_and_prep.length() ).stripWhiteSpace() );
+ }
+
+ //header (if present)
+ new_ingredient.group = current_header;
+
+ bool last_is_sub = is_sub;
+ if ( new_ingredient.prepMethodList.last().name == "or" ) {
+ new_ingredient.prepMethodList.pop_back();
+ is_sub = true;
+ }
+ else
+ is_sub = false;
+
+ if ( last_is_sub )
+ ( *recipe.ingList.at( recipe.ingList.count() - 1 ) ).substitutes.append(new_ingredient);
+ else
+ recipe.ingList.append( new_ingredient );
+}
+
+void RezkonvImporter::loadIngredientHeader( const QString &string, Recipe &/*recipe*/ )
+{
+ QString header = string;
+ header.remove( QRegExp( "^=*" ) ).remove( QRegExp( "=*$" ) );
+ header = header.stripWhiteSpace();
+
+ kdDebug() << "found ingredient header: " << header << endl;
+
+ current_header = header;
+}
+
+void RezkonvImporter::loadInstructions( QStringList::const_iterator &text_it, Recipe &recipe )
+{
+ QString instr;
+ QRegExp rx_title( "^:{0,1}\\s*O-Titel\\s*:" );
+ QString line;
+ while ( text_it != m_end_it ) {
+ line = *text_it;
+
+ //titles longer than the line width are rewritten here
+ if ( line.contains( rx_title ) ) {
+ line.remove( rx_title );
+ recipe.title = line.stripWhiteSpace();
+
+ QRegExp rx_line_cont( ":\\s*>{0,1}\\s*:" );
+ while ( ( line = *text_it ).contains( rx_line_cont ) ) {
+ line.remove( rx_line_cont );
+ recipe.title += line;
+
+ text_it++;
+ }
+ kdDebug() << "Found long title: " << recipe.title << endl;
+ }
+ else {
+ if ( line.isEmpty() )
+ instr += "\n\n";
+
+ instr += line;
+ }
+
+ text_it++;
+ }
+
+ recipe.instructions = instr;
+}
+
+void RezkonvImporter::loadReferences( QStringList::const_iterator &text_it, Recipe &recipe )
+{
+ kdDebug() << "Found source header" << endl;
+
+ while ( text_it != m_end_it ) {
+ text_it++;
+ QRegExp rx_line_begin( "^\\s*-{0,2}\\s*" );
+
+ QRegExp rx_creation_date = QRegExp( "^\\s*-{0,2}\\s*Erfasst \\*RK\\*", false );
+ if ( ( *text_it ).contains( rx_creation_date ) ) // date followed by typist
+ {
+ QString date = *text_it;
+ date.remove( rx_creation_date ).remove( QRegExp( " von\\s*$" ) );
+
+ // Date is given as DD.MM.YY
+ QString s = date.section( ".", 0, 0 );
+ int day = s.toInt();
+
+ s = date.section( ".", 1, 1 );
+ int month = s.toInt();
+
+ s = date.section( ".", 2, 2 );
+ int year = s.toInt();
+ year += 1900;
+ if ( year < 1970 )
+ year += 100; //we'll assume nothing has been created before 1970 (y2k issues :p)
+
+ recipe.ctime = QDate(year,month,day);
+
+ #if 0
+ //typist
+ text_it++;
+ QString typist = = *text_it;
+ typist.remove( rx_line_begin );
+ #endif
+
+ }
+ else //everything else is an author
+ {
+ if ( ( *text_it ).contains( rx_line_begin ) ) {
+ QString author = *text_it;
+ author.remove( rx_line_begin );
+
+ recipe.authorList.append( Element( author ) );
+ }
+ else
+ break;
+ }
+ }
+}
+
+void RezkonvImporter::readRange( const QString &range_str, double &amount, double &amount_offset )
+{
+ QString from = range_str.section( '-', 0, 0 );
+ QString to = range_str.section( '-', 1, 1 );
+
+ amount = MixedNumber::fromString( from ).toDouble();
+
+ if ( !to.stripWhiteSpace().isEmpty() )
+ amount_offset = MixedNumber::fromString( to ).toDouble() - amount;
+}
diff --git a/krecipes/src/importers/rezkonvimporter.h b/krecipes/src/importers/rezkonvimporter.h
new file mode 100644
index 0000000..2b0b707
--- /dev/null
+++ b/krecipes/src/importers/rezkonvimporter.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef REZKONVIMPORTER_H
+#define REZKONVIMPORTER_H
+
+#include <qstringlist.h>
+
+#include "baseimporter.h"
+#include "datablocks/recipe.h"
+
+class RezkonvImporter : public BaseImporter
+{
+public:
+ RezkonvImporter();
+ ~RezkonvImporter();
+
+protected:
+ void parseFile( const QString &filename );
+
+private:
+ void loadIngredient( const QString &line, Recipe & recipe, bool &is_sub );
+ void loadIngredientHeader( const QString &line, Recipe & recipe );
+ void loadInstructions( QStringList::const_iterator &raw_text, Recipe & recipe );
+ void loadReferences( QStringList::const_iterator &raw_text, Recipe & recipe );
+
+ void readRange( const QString &, double &amount, double &amount_offset );
+ void readRecipe( const QStringList &raw_text );
+
+ QStringList::const_iterator m_end_it;
+
+ QString current_header;
+};
+
+#endif //REZKONVIMPORTER_H
diff --git a/krecipes/src/klomanager.cpp b/krecipes/src/klomanager.cpp
new file mode 100644
index 0000000..b760dc7
--- /dev/null
+++ b/krecipes/src/klomanager.cpp
@@ -0,0 +1,218 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "klomanager.h"
+
+#include <kdebug.h>
+
+#include <qdom.h>
+#include <qfile.h>
+#include <qstringlist.h>
+
+KLOManager::KLOManager()
+{
+}
+
+KLOManager::~KLOManager()
+{
+
+}
+
+QStringList KLOManager::classes()
+{
+ QStringList classesList;
+ classesList << "title" << "instructions" << "yield" << "prep_time" << "photo" << "authors" <<
+ "categories" << "header" << "ingredients" << "properties" << "ratings";
+ return classesList;
+}
+
+void KLOManager::processDocument( const QDomDocument &doc )
+{
+ QDomElement layout = doc.documentElement();
+
+ if ( layout.tagName() != "krecipes-layout" ) {
+ kdDebug() << "This file does not appear to be a valid Krecipes layout file." << endl;
+ return ;
+ }
+
+ QDomNodeList l = layout.childNodes();
+ for ( unsigned int i = 0 ; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+ QString tagName = el.tagName();
+ QDomNodeList subList = el.childNodes();
+ /*if ( !*/beginObject( tagName )/* ) {*/; //###: just a thought....
+ for ( unsigned int j = 0 ; j < subList.count(); j++ ) {
+ QDomElement subEl = subList.item( j ).toElement();
+ QString subTagName = subEl.tagName();
+
+ if ( subTagName == "background-color" )
+ loadBackgroundColor( tagName, getColorAttribute(el,subTagName) );
+ else if ( subTagName == "font" )
+ loadFont( tagName, getFontAttribute(el,subTagName) );
+ else if ( subTagName == "text-color" )
+ loadTextColor( tagName, getColorAttribute(el,subTagName) );
+ else if ( subTagName == "visible" )
+ loadVisibility( tagName, getBoolAttribute(el,subTagName) );
+ else if ( subTagName == "alignment" )
+ loadAlignment( tagName, getIntAttribute(el,subTagName) );
+ else if ( subTagName == "border" )
+ loadBorder( tagName, getBorderAttribute(el,subTagName) );
+ else if ( subTagName == "columns" )
+ loadColumns( tagName, getIntAttribute(el,subTagName) );
+ else
+ kdDebug() << "Warning: Unknown tag within <" << tagName << ">: " << subTagName << endl;
+ }
+ endObject();
+ }
+}
+
+QDomElement KLOManager::getLayoutAttribute( const QDomElement &object, const QString &attribute ) const
+{
+ QDomNodeList l = object.childNodes();
+ for ( unsigned i = 0; i < l.count(); i++ ) {
+ QDomElement el = l.item( i ).toElement();
+
+ if ( el.tagName() == attribute )
+ return el;
+ }
+
+ kdDebug() << "Warning: Requested attribute \"" << attribute << "\" not found." << endl;
+ return QDomElement();
+}
+
+bool KLOManager::getBoolAttribute( const QDomElement &object, const QString &attribute, bool defaultValue ) const
+{
+ QDomElement result = getLayoutAttribute( object, attribute );
+ if ( result.isNull() ) {
+ return defaultValue;
+ }
+ else {
+ return result.text() == "true";
+ }
+}
+
+QColor KLOManager::getColorAttribute( const QDomElement &object, const QString &attribute, const QColor &defaultValue ) const
+{
+ QDomElement result = getLayoutAttribute( object, attribute );
+ if ( result.isNull() ) {
+ return defaultValue;
+ }
+ else {
+ return QColor(result.text());
+ }
+}
+
+QString KLOManager::getTextAttribute( const QDomElement &object, const QString &attribute, const QString &defaultValue ) const
+{
+ QDomElement result = getLayoutAttribute( object, attribute );
+ if ( result.isNull() ) {
+ return defaultValue;
+ }
+ else {
+ return result.text();
+ }
+}
+
+int KLOManager::getIntAttribute( const QDomElement &object, const QString &attribute, int defaultValue ) const
+{
+ QDomElement result = getLayoutAttribute( object, attribute );
+ if ( result.isNull() ) {
+ return defaultValue;
+ }
+ else {
+ return result.text().toInt();
+ }
+}
+
+KreBorder KLOManager::getBorderAttribute( const QDomElement &object, const QString &attribute, const KreBorder &defaultValue ) const
+{
+ QDomElement result = getLayoutAttribute( object, attribute );
+ if ( result.isNull() ) {
+ return defaultValue;
+ }
+ else {
+ return KreBorder( result.attribute( "width" ).toInt(), result.attribute( "style" ), QColor(result.attribute( "color" )) );
+ }
+}
+
+QFont KLOManager::getFontAttribute( const QDomElement &object, const QString &attribute, const QFont &defaultValue ) const
+{
+ QDomElement result = getLayoutAttribute( object, attribute );
+ if ( result.isNull() ) {
+ return defaultValue;
+ }
+ else {
+ QFont font;
+ font.fromString(result.text());
+ return font;
+ }
+}
+
+QString KLOManager::alignmentAsCSS( int alignment )
+{
+ QString text;
+
+ if ( alignment & Qt::AlignLeft )
+ text += "text-align: left;\n";
+ if ( alignment & Qt::AlignRight )
+ text += "text-align: right;\n";
+ if ( alignment & Qt::AlignHCenter )
+ text += "text-align: center;\n";
+ if ( alignment & Qt::AlignTop )
+ text += "vertical-align: top;\n";
+ if ( alignment & Qt::AlignBottom )
+ text += "vertical-align: bottom;\n";
+ if ( alignment & Qt::AlignVCenter )
+ text += "vertical-align: middle;\n";
+
+ return text;
+}
+
+QString KLOManager::borderAsCSS( const KreBorder &border )
+{
+ return QString( "border: %1px %2 %3;\n" ).arg(border.width).arg(border.style).arg(border.color.name());
+}
+
+QString KLOManager::bgColorAsCSS( const QColor &color )
+{
+ return QString( "background-color: %1;\n" ).arg( color.name() );
+}
+
+QString KLOManager::fontAsCSS( const QFont &font )
+{
+ QString text;
+
+ text += QString( "font-family: %1;\n" ).arg( font.family() );
+ text += QString( "font-weight: %1;\n" ).arg( font.weight() );
+ text += QString( "font-size: %1pt;\n" ).arg( font.pointSize() );
+ if ( font.underline() )
+ text += "text-decoration: underline;\n";
+ if ( font.strikeOut() )
+ text += "text-decoration: line-through;\n";
+ if ( font.bold() )
+ text += "font-weight: bold;\n";
+ if ( font.italic() )
+ text += "font-style: italic;\n";
+
+ return text;
+}
+
+QString KLOManager::textColorAsCSS( const QColor &color )
+{
+ return QString( "color: %1;\n" ).arg( color.name() );
+}
+
+QString KLOManager::visibilityAsCSS( bool visible )
+{
+ if ( visible )
+ return "visibility: visible;\n";
+ else
+ return "visibility: hidden;\n";
+}
diff --git a/krecipes/src/klomanager.h b/krecipes/src/klomanager.h
new file mode 100644
index 0000000..ed90771
--- /dev/null
+++ b/krecipes/src/klomanager.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KLOMANAGER_H
+#define KLOMANAGER_H
+
+#include <qdom.h>
+#include <qfont.h>
+
+#include "datablocks/kreborder.h"
+
+class KLOManager
+{
+public:
+ KLOManager();
+ virtual ~KLOManager();
+
+ static QStringList classes();
+
+protected:
+ void processDocument( const QDomDocument & );
+
+ virtual void loadBackgroundColor( const QString &/*obj*/, const QColor& ){};
+ virtual void loadFont( const QString &/*obj*/, const QFont& ){};
+ virtual void loadTextColor( const QString &/*obj*/, const QColor& ){};
+ virtual void loadVisibility( const QString &/*obj*/, bool ){};
+ virtual void loadAlignment( const QString &/*obj*/, int ){};
+ virtual void loadBorder( const QString &/*obj*/, const KreBorder& ){};
+ virtual void loadColumns( const QString &/*obj*/, int ){};
+
+ virtual void beginObject( const QString &/*obj*/ ){};
+ virtual void endObject(){};
+
+ bool getBoolAttribute( const QDomElement &obj, const QString &attr, bool defaultValue = true ) const;
+ QColor getColorAttribute( const QDomElement &obj, const QString &attr, const QColor &defaultValue = Qt::white ) const;
+ QString getTextAttribute( const QDomElement &obj, const QString &attr, const QString &defaultValue = QString::null ) const;
+ int getIntAttribute( const QDomElement &obj, const QString &attr, int defaultValue = 0 ) const;
+ KreBorder getBorderAttribute( const QDomElement &obj, const QString &attr, const KreBorder &defaultValue = KreBorder() ) const;
+ QFont getFontAttribute( const QDomElement &obj, const QString &attr, const QFont &defaultValue = QFont() ) const;
+
+ QString alignmentAsCSS( int );
+ QString borderAsCSS( const KreBorder & );
+ QString bgColorAsCSS( const QColor & );
+ QString fontAsCSS( const QFont & );
+ QString textColorAsCSS( const QColor & );
+ QString visibilityAsCSS( bool );
+
+private:
+ QDomElement getLayoutAttribute( const QDomElement &obj, const QString &attr ) const;
+};
+
+#endif //KLOMANAGER_H
diff --git a/krecipes/src/krecipes.cpp b/krecipes/src/krecipes.cpp
new file mode 100644
index 0000000..845c29b
--- /dev/null
+++ b/krecipes/src/krecipes.cpp
@@ -0,0 +1,703 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* Copyright (C) 2003-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "pref.h"
+#include "krecipes.h"
+#include "krecipesview.h"
+
+#include "dialogs/recipeviewdialog.h"
+#include "dialogs/recipeinputdialog.h"
+#include "dialogs/selectrecipedialog.h"
+#include "dialogs/ingredientsdialog.h"
+#include "dialogs/propertiesdialog.h"
+#include "dialogs/shoppinglistdialog.h"
+#include "dialogs/categorieseditordialog.h"
+#include "dialogs/authorsdialog.h"
+#include "dialogs/unitsdialog.h"
+#include "dialogs/ingredientmatcherdialog.h"
+#include "dialogs/dbimportdialog.h"
+#include "dialogs/pagesetupdialog.h"
+#include "dialogs/recipeimportdialog.h"
+#include "dialogs/similarcategoriesdialog.h"
+#include "dialogs/conversiondialog.h"
+
+#include "importers/kreimporter.h"
+#include "importers/mmfimporter.h"
+#include "importers/mx2importer.h"
+#include "importers/mxpimporter.h"
+#include "importers/nycgenericimporter.h"
+#include "importers/recipemlimporter.h"
+#include "importers/rezkonvimporter.h"
+#include "importers/kredbimporter.h"
+
+#include "datablocks/recipe.h"
+#include "backends/recipedb.h"
+#include "backends/progressinterface.h"
+
+#include <qdragobject.h>
+#include <kprinter.h>
+#include <qpainter.h>
+#include <qpaintdevicemetrics.h>
+#include <qmessagebox.h>
+
+#include <kprogress.h>
+#include <kmessagebox.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmenubar.h>
+#include <kstatusbar.h>
+#include <kkeydialog.h>
+#include <kaccel.h>
+#include <kio/netaccess.h>
+#include <kfiledialog.h>
+#include <kconfig.h>
+#include <kcursor.h>
+
+#include <kedittoolbar.h>
+#include <kstdaccel.h>
+#include <kaction.h>
+#include <kstdaction.h>
+//Settings headers
+#include <kdeversion.h>
+
+Krecipes::Krecipes()
+ : KMainWindow( 0, "Krecipes" ),
+ m_view( new KrecipesView( this ) ),
+ m_printer( 0 )
+{
+ // accept dnd
+ setAcceptDrops( true );
+
+ // tell the KMainWindow that this is indeed the main widget
+ setCentralWidget( m_view );
+
+ // then, setup our actions
+ setupActions();
+
+ // and a status bar
+ statusBar() ->show();
+
+ // apply the saved mainwindow settings, if any, and ask the mainwindow
+ // to automatically save settings if changed: window size, toolbar
+ // position, icon size, etc.
+ setAutoSaveSettings();
+
+
+ // allow the view to change the statusbar and caption
+ connect( m_view, SIGNAL( signalChangeStatusbar( const QString& ) ),
+ this, SLOT( changeStatusbar( const QString& ) ) );
+ connect( m_view, SIGNAL( signalChangeCaption( const QString& ) ),
+ this, SLOT( changeCaption( const QString& ) ) );
+
+ connect( m_view, SIGNAL( panelShown( KrePanel, bool ) ), SLOT( updateActions( KrePanel, bool ) ) );
+ connect( m_view, SIGNAL( panelShown( KrePanel, bool ) ), SLOT( updateActions( KrePanel, bool ) ) );
+
+ connect( m_view, SIGNAL( recipeSelected(bool) ), SLOT( recipeSelected(bool) ) );
+
+
+ // Enable/Disable the Save Button (Initialize disabled, and connect signal)
+
+ connect( m_view, SIGNAL( enableSaveOption( bool ) ), this, SLOT( enableSaveOption( bool ) ) );
+
+ enableSaveOption( false ); // Disables saving initially
+ recipeSelected( false ); //nothing is selected initially
+
+ parsing_file_dlg = new KDialog( this, "parsing_file_dlg", true, Qt::WX11BypassWM );
+ QLabel *parsing_file_dlg_label = new QLabel( i18n( "Gathering recipe data from file.\nPlease wait..." ), parsing_file_dlg );
+ parsing_file_dlg_label->setFrameStyle( QFrame::Box | QFrame::Raised );
+ ( new QVBoxLayout( parsing_file_dlg ) ) ->addWidget( parsing_file_dlg_label );
+ parsing_file_dlg->adjustSize();
+ //parsing_file_dlg->setFixedSize(parsing_file_dlg->size());
+
+ convertDialog = new ConversionDialog(this,m_view->database);
+}
+
+Krecipes::~Krecipes()
+{}
+
+void Krecipes::updateActions( KrePanel panel, bool show )
+{
+ switch ( panel ) {
+ case RecipeView: {
+ exportAction->setEnabled( show );
+ printAction->setEnabled( show );
+ reloadAction->setEnabled( show );
+ copyToClipboardAction->setEnabled( show );
+
+ //can't edit when there are multiple recipes loaded
+ if ( show && m_view->viewPanel->recipesLoaded() == 1 ) {
+ editAction->setEnabled( true );
+ }
+ else
+ editAction->setEnabled( false );
+
+ break;
+ }
+ case SelectP: {
+ exportAction->setEnabled( show );
+ editAction->setEnabled( show );
+ copyToClipboardAction->setEnabled( show );
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void Krecipes::recipeSelected( bool selected )
+{
+ copyToClipboardAction->setEnabled( selected );
+ editAction->setEnabled( selected );
+}
+
+void Krecipes::setupActions()
+{
+ printAction = KStdAction::print( this, SLOT( filePrint() ), actionCollection() );
+ reloadAction = new KAction( i18n( "Reloa&d" ), "reload", Key_F5, m_view, SLOT( reloadDisplay() ), actionCollection(), "reload_action" );
+
+ editAction = new KAction( i18n( "&Edit Recipe" ), "edit", CTRL + Key_E,
+ m_view, SLOT( editRecipe() ),
+ actionCollection(), "edit_action" );
+
+ (void) new KAction( i18n( "&Measurement Converter" ), "", CTRL + Key_M,
+ this, SLOT( conversionToolSlot() ),
+ actionCollection(), "converter_action" );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup("Advanced");
+ if ( config->readBoolEntry("UnhideMergeTools",false) ) {
+ ( void ) new KAction( i18n( "&Merge Similar Categories..." ), "categories", CTRL + Key_M,
+ this, SLOT( mergeSimilarCategories() ),
+ actionCollection(), "merge_categories_action" );
+
+ ( void ) new KAction( i18n( "&Merge Similar Ingredients..." ), "ingredients", CTRL + Key_M,
+ this, SLOT( mergeSimilarIngredients() ),
+ actionCollection(), "merge_ingredients_action" );
+ }
+
+ KAction *action = KStdAction::openNew( this, SLOT( fileNew() ), actionCollection() );
+ action->setText( i18n( "&New Recipe" ) );
+
+ saveAction = KStdAction::save( this, SLOT( fileSave() ), actionCollection() );
+
+ KStdAction::quit( kapp, SLOT( quit() ), actionCollection() );
+
+ m_toolbarAction = KStdAction::showToolbar( this, SLOT( optionsShowToolbar() ), actionCollection() );
+ m_statusbarAction = KStdAction::showStatusbar( this, SLOT( optionsShowStatusbar() ), actionCollection() );
+
+ KStdAction::keyBindings( this, SLOT( optionsConfigureKeys() ), actionCollection() );
+ KStdAction::configureToolbars( this, SLOT( optionsConfigureToolbars() ), actionCollection() );
+ KStdAction::preferences( this, SLOT( optionsPreferences() ), actionCollection() );
+
+ ( void ) new KAction( i18n( "Import from File..." ), CTRL + Key_I,
+ this, SLOT( import() ),
+ actionCollection(), "import_action" );
+
+ ( void ) new KAction( i18n( "Import from Database..." ), 0,
+ this, SLOT( kreDBImport() ),
+ actionCollection(), "import_db_action" );
+
+ exportAction = new KAction( i18n( "Export..." ), 0,
+ this, SLOT( fileExport() ),
+ actionCollection(), "export_action" );
+
+ copyToClipboardAction = new KAction( i18n( "&Copy to Clipboard" ), "editcopy",
+ CTRL + Key_C,
+ this, SLOT( fileToClipboard() ),
+ actionCollection(), "copy_to_clipboard_action" );
+
+ ( void ) new KAction( i18n( "Page Setup..." ), 0,
+ this, SLOT( pageSetupSlot() ),
+ actionCollection(), "page_setup_action" );
+
+ ( void ) new KAction( i18n( "Print Setup..." ), 0,
+ this, SLOT( printSetupSlot() ),
+ actionCollection(), "print_setup_action" );
+
+ ( void ) new KAction( i18n( "Backup..." ), "krecipes_file", 0,
+ this, SLOT( backupSlot() ),
+ actionCollection(), "backup_action" );
+
+ ( void ) new KAction( i18n( "Restore..." ), 0,
+ this, SLOT( restoreSlot() ),
+ actionCollection(), "restore_action" );
+
+ updateActions( SelectP, true );
+ updateActions( RecipeView, false );
+
+ createGUI();
+}
+
+void Krecipes::saveProperties( KConfig * )
+{
+ // the 'config' object points to the session managed
+ // config file. anything you write here will be available
+ // later when this app is restored
+
+ //if (!m_view->currentURL().isNull())
+ // config->writeEntry("lastURL", m_view->currentURL());
+}
+
+void Krecipes::readProperties( KConfig * )
+{
+ // the 'config' object points to the session managed
+ // config file. this function is automatically called whenever
+ // the app is being restored. read in here whatever you wrote
+ // in 'saveProperties'
+
+ //QString url = config->readEntry("lastURL");
+
+ //if (!url.isNull())
+ // m_view->openURL(KURL(url));
+}
+
+void Krecipes::dragEnterEvent( QDragEnterEvent *event )
+{
+ // accept uri drops only
+ event->accept( QUriDrag::canDecode( event ) );
+}
+
+
+void Krecipes::fileNew()
+{
+
+ // Create a new element (Element depends on active panel. New recipe by default)
+ m_view->createNewElement();
+}
+
+void Krecipes::fileOpen()
+{
+ // this slot is called whenever the File->Open menu is selected,
+ // the Open shortcut is pressed (usually CTRL+O) or the Open toolbar
+ // button is clicked
+ /*
+ // this brings up the generic open dialog
+ KURL url = KURLRequesterDlg::getURL(QString::null, this, i18n("Open Location") );
+ */
+ // standard filedialog
+ /*KURL url = KFileDialog::getOpenURL(QString::null, QString::null, this, i18n("Open Location"));
+ if (!url.isEmpty())
+ m_view->openURL(url);*/
+}
+
+void Krecipes::fileSave()
+{
+ // this slot is called whenever the File->Save menu is selected,
+ // the Save shortcut is pressed (usually CTRL+S) or the Save toolbar
+ // button is clicked
+ m_view->save();
+}
+
+void Krecipes::fileExport()
+{
+ // this slot is called whenever the File->Export menu is selected,
+ m_view->exportRecipe();
+}
+
+void Krecipes::fileToClipboard()
+{
+ m_view->exportToClipboard();
+}
+
+void Krecipes::filePrint()
+{
+ m_view->print();
+}
+
+void Krecipes::import()
+{
+ KFileDialog file_dialog( QString::null,
+ "*.kre *.kreml|Krecipes (*.kre, *.kreml)\n"
+ "*.mx2|MasterCook (*.mx2)\n"
+ "*.mxp *.txt|MasterCook Export (*.mxp, *.txt)\n"
+ "*.mmf *.txt|Meal-Master (*.mmf, *.txt)\n"
+ "*.txt|\"Now You're Cooking\" Generic Export (*.txt)\n"
+ "*.xml *.recipeml|RecipeML (*.xml, *.recipeml)\n"
+ "*.rk *.txt|Rezkonv (*.rk, *.txt)",
+ this,
+ "file_dialog",
+ true
+ );
+ file_dialog.setMode( KFile::Files );
+
+ if ( file_dialog.exec() == KFileDialog::Accepted ) {
+ QStringList warnings_list;
+
+ QString selected_filter = file_dialog.currentFilter();
+
+ BaseImporter *importer;
+ if ( selected_filter == "*.mxp *.txt" )
+ importer = new MXPImporter();
+ else if ( selected_filter == "*.mmf *.txt" )
+ importer = new MMFImporter();
+ else if ( selected_filter == "*.txt" )
+ importer = new NYCGenericImporter();
+ else if ( selected_filter == "*.mx2" )
+ importer = new MX2Importer();
+ else if ( selected_filter == "*.kre *.kreml" )
+ importer = new KreImporter();
+ else if ( selected_filter == "*.xml *.recipeml" )
+ importer = new RecipeMLImporter();
+ else if ( selected_filter == "*.rk *.txt" )
+ importer = new RezkonvImporter();
+ else {
+ KMessageBox::sorry( this,
+ QString( i18n( "Filter \"%1\" not recognized.\n"
+ "Please select one of the provided filters." ) ).arg( selected_filter ),
+ i18n( "Unrecognized Filter" )
+ );
+ import(); //let's try again :)
+ return ;
+ }
+
+ parsing_file_dlg->show();
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+ importer->parseFiles( file_dialog.selectedFiles() );
+ parsing_file_dlg->hide();
+ KApplication::restoreOverrideCursor();
+
+ KConfig * config = kapp->config();
+ config->setGroup( "Import" );
+ bool direct = config->readBoolEntry( "DirectImport", false );
+ if ( !direct ) {
+ RecipeImportDialog import_dialog( importer->recipeList() );
+
+ if ( import_dialog.exec() != QDialog::Accepted ) {
+ delete importer;
+ return;
+ }
+ else
+ importer->setRecipeList( import_dialog.getSelectedRecipes() );
+ }
+
+ importer->import(m_view->database);
+ //m_view->database->import( importer ); //TODO TESTS: Do it this way
+
+ if ( !importer->getMessages().isEmpty() ) {
+ KTextEdit * warningEdit = new KTextEdit( this );
+ warningEdit->setTextFormat( Qt::RichText );
+ warningEdit->setText( QString( i18n( "NOTE: We recommend that all recipes generating warnings be checked to ensure that they were properly imported, and no loss of recipe data has occurred.<br><br>" ) ) + importer->getMessages() );
+ warningEdit->setReadOnly( true );
+
+ KDialogBase showWarningsDlg( KDialogBase::Swallow, i18n( "Import Warnings" ), KDialogBase::Ok, KDialogBase::Default, this );
+ showWarningsDlg.setMainWidget( warningEdit ); //KDialogBase will delete warningEdit for us
+ showWarningsDlg.resize( QSize( 550, 250 ) );
+ showWarningsDlg.exec();
+ }
+
+ delete importer;
+ }
+}
+
+void Krecipes::kreDBImport()
+{
+ DBImportDialog importOptions;
+ if ( importOptions.exec() == QDialog::Accepted ) {
+ //Get all params, even if we don't use them
+ QString host, user, pass, table;
+ int port;
+ importOptions.serverParams( host, user, pass, port, table );
+
+ KreDBImporter importer( importOptions.dbType(), host, user, pass, port ); //last 4 params may or may not be even used (depends on dbType)
+
+ parsing_file_dlg->show();
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+ QStringList tables;
+ if ( importOptions.dbType() == "SQLite" )
+ tables << importOptions.dbFile();
+ else //MySQL or PostgreSQL
+ tables << table;
+ importer.parseFiles( tables );
+ parsing_file_dlg->hide();
+ KApplication::restoreOverrideCursor();
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Import" );
+ bool direct = config->readBoolEntry( "DirectImport", false );
+ if ( !direct ) {
+ RecipeImportDialog import_dialog( importer.recipeList() );
+
+ if ( import_dialog.exec() != QDialog::Accepted ) {
+ return;
+ }
+ else
+ importer.setRecipeList( import_dialog.getSelectedRecipes() );
+ }
+
+ QString error = importer.getErrorMsg();
+ if ( !error.isEmpty() ) {
+ KMessageBox::sorry( this, error );
+ }
+ else
+ importer.import(m_view->database);
+ }
+}
+
+void Krecipes::pageSetupSlot()
+{
+ Recipe recipe;
+ m_view->selectPanel->getCurrentRecipe( &recipe );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup("Page Setup");
+ QString layout = config->readEntry( "Layout", locate( "appdata", "layouts/Default.klo" ) );
+ QString printLayout = config->readEntry( "PrintLayout", locate( "appdata", "layouts/Default.klo" ) );
+
+ if ( layout == printLayout ) {
+ KMessageBox::information( this, i18n("The recipe print and view layouts use the same file for their style, meaning changing one view's look changes them both. If this is not the behavior you desire, load one style and save it under a different name."),
+ QString::null, "sharedLayoutWarning" );
+ }
+
+ PageSetupDialog page_setup( this, recipe );
+ page_setup.exec();
+}
+
+void Krecipes::printSetupSlot()
+{
+ Recipe recipe;
+ m_view->selectPanel->getCurrentRecipe( &recipe );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup("Page Setup");
+ QString layout = config->readEntry( "Layout", locate( "appdata", "layouts/Default.klo" ) );
+ QString printLayout = config->readEntry( "PrintLayout", locate( "appdata", "layouts/Default.klo" ) );
+
+ if ( layout == printLayout ) {
+ KMessageBox::information( this, i18n("The recipe print and view layouts use the same file for their style, meaning changing one view's look changes them both. If this is not the behavior you desire, load one style and save it under a different name."),
+ QString::null, "sharedLayoutWarning" );
+ }
+
+ PageSetupDialog pageSetup( this, recipe, "Print" );
+ pageSetup.exec();
+}
+
+void Krecipes::conversionToolSlot()
+{
+ convertDialog->show();
+}
+
+void Krecipes::backupSlot()
+{
+ QString fileName = KFileDialog::getSaveFileName(QString::null,
+ QString("*.krecbk|%1 (*.krecbk)").arg("Krecipes Backup File"),
+ this,i18n("Save Backup As..."));
+
+ int overwrite = KMessageBox::Yes;
+ if ( QFile::exists(fileName) ) {
+ overwrite = KMessageBox::warningYesNo( this, QString( i18n( "File \"%1\" exists. Are you sure you want to overwrite it?" ) ).arg( fileName ) );
+ }
+
+ if ( !fileName.isNull() && overwrite == KMessageBox::Yes ) {
+ ProgressInterface pi(this);
+ pi.listenOn(m_view->database);
+
+ QString errMsg;
+ if ( !m_view->database->backup( fileName, &errMsg ) )
+ KMessageBox::error( this, errMsg, i18n("Backup Failed") );
+ }
+}
+
+void Krecipes::restoreSlot()
+{
+ QString filename = KFileDialog::getOpenFileName(QString::null,
+ QString("*.krecbk|%1 (*.krecbk)").arg(i18n("Krecipes Backup File")),
+ this,i18n("Restore Backup"));
+
+ if ( !filename.isNull() ) {
+ switch ( KMessageBox::warningContinueCancel(this,i18n("<b>Restoring this file will erase ALL data currently in the database!</b><br /><br />If you want to keep the recipes in your database, click \"Cancel\" and first export your recipes. These can then be imported once the restore is complete.<br /><br />Are you sure you want to proceed?"),QString::null,KStdGuiItem::cont(),"RestoreWarning") ) {
+ case KMessageBox::Continue: {
+ ProgressInterface pi(this);
+ pi.listenOn(m_view->database);
+
+ QString errMsg;
+ if ( m_view->database->restore( filename, &errMsg ) )
+ KMessageBox::information(this,i18n("Restore successful."));
+ else
+ KMessageBox::error( this, errMsg, i18n("Restore Failed") );
+
+ pi.listenOn(0);
+ m_view->reload();
+ }
+ case KMessageBox::Cancel:
+ default: break;
+ }
+ }
+}
+
+void Krecipes::mergeSimilarCategories()
+{
+ ElementList categories;
+ m_view->database->loadCategories(&categories);
+ SimilarCategoriesDialog dlg(categories,this);
+ if ( dlg.exec() == QDialog::Accepted ) {
+ QValueList<int> ids = dlg.matches();
+ QString name = dlg.element();
+
+ int id = m_view->database->findExistingCategoryByName(name);
+ if ( id == -1 ) {
+ m_view->database->createNewCategory(name);
+ id = m_view->database->lastInsertID();
+ }
+
+ for ( QValueList<int>::const_iterator it = ids.begin(); it != ids.end(); ++it ) {
+ if ( id != *it )
+ m_view->database->mergeCategories(id, *it);
+ }
+ }
+}
+
+void Krecipes::mergeSimilarIngredients()
+{
+ ElementList ingredients;
+ m_view->database->loadIngredients(&ingredients);
+ SimilarCategoriesDialog dlg(ingredients,this);
+ if ( dlg.exec() == QDialog::Accepted ) {
+ QValueList<int> ids = dlg.matches();
+ QString name = dlg.element();
+
+ if ( ids.isEmpty() || name.isEmpty() ) return;
+
+ int id = m_view->database->findExistingIngredientByName(name);
+ if ( id == -1 ) {
+ m_view->database->createNewIngredient(name);
+ id = m_view->database->lastInsertID();
+ }
+
+ for ( QValueList<int>::const_iterator it = ids.begin(); it != ids.end(); ++it ) {
+ if ( id != *it )
+ m_view->database->mergeIngredients(id, *it);
+ }
+ }
+}
+
+//return true to close app
+bool Krecipes::queryClose()
+{
+ if ( !m_view->inputPanel->everythingSaved() ) {
+ switch ( KMessageBox::questionYesNoCancel( this,
+ i18n( "A recipe contains unsaved changes.\n"
+ "Do you want to save the changes before exiting?" ),
+ i18n( "Unsaved Changes" ) ) ) {
+ case KMessageBox::Yes:
+ return m_view->save();
+ case KMessageBox::No:
+ return true;
+ case KMessageBox::Cancel:
+ return false;
+ default:
+ return true;
+ }
+ }
+ else
+ return true;
+}
+
+void Krecipes::optionsShowToolbar()
+{
+ // this is all very cut and paste code for showing/hiding the
+ // toolbar
+ if ( m_toolbarAction->isChecked() )
+ toolBar() ->show();
+ else
+ toolBar() ->hide();
+}
+
+void Krecipes::optionsShowStatusbar()
+{
+ // this is all very cut and paste code for showing/hiding the
+ // statusbar
+ if ( m_statusbarAction->isChecked() )
+ statusBar() ->show();
+ else
+ statusBar() ->hide();
+}
+
+void Krecipes::optionsConfigureKeys()
+{
+#if KDE_IS_VERSION(3,1,92 )
+ // for KDE 3.2: KKeyDialog::configureKeys is deprecated
+ KKeyDialog::configure( actionCollection(), this, true );
+#else
+
+ KKeyDialog::configureKeys( actionCollection(), "krecipesui.rc" );
+#endif
+
+}
+
+void Krecipes::optionsConfigureToolbars()
+{
+ // use the standard toolbar editor
+#if defined(KDE_MAKE_VERSION)
+# if KDE_VERSION >= KDE_MAKE_VERSION(3,1,0)
+ saveMainWindowSettings( KGlobal::config(), autoSaveGroup() );
+# else
+
+ saveMainWindowSettings( KGlobal::config() );
+# endif
+#else
+
+ saveMainWindowSettings( KGlobal::config() );
+#endif
+
+ KEditToolbar dlg( actionCollection() );
+ connect( &dlg, SIGNAL( newToolbarConfig() ), this, SLOT( newToolbarConfig() ) );
+ dlg.exec();
+}
+
+void Krecipes::newToolbarConfig()
+{
+ // this slot is called when user clicks "Ok" or "Apply" in the toolbar editor.
+ // recreate our GUI, and re-apply the settings (e.g. "text under icons", etc.)
+ createGUI();
+
+#if defined(KDE_MAKE_VERSION)
+# if KDE_VERSION >= KDE_MAKE_VERSION(3,1,0)
+
+ applyMainWindowSettings( KGlobal::config(), autoSaveGroup() );
+# else
+
+ applyMainWindowSettings( KGlobal::config() );
+# endif
+#else
+
+ applyMainWindowSettings( KGlobal::config() );
+#endif
+}
+
+void Krecipes::optionsPreferences()
+{
+
+ // popup some sort of preference dialog, here
+ KrecipesPreferences dlg( this );
+ if ( dlg.exec() ) {}
+
+}
+
+void Krecipes::changeStatusbar( const QString& text )
+{
+ // display the text on the statusbar
+ statusBar() ->message( text );
+}
+
+void Krecipes::changeCaption( const QString& text )
+{
+ // display the text on the caption
+ setCaption( text );
+}
+void Krecipes::enableSaveOption( bool en )
+{
+ saveAction->setEnabled( en );
+}
+
+#include "krecipes.moc"
diff --git a/krecipes/src/krecipes.desktop b/krecipes/src/krecipes.desktop
new file mode 100644
index 0000000..e04f242
--- /dev/null
+++ b/krecipes/src/krecipes.desktop
@@ -0,0 +1,66 @@
+[Desktop Entry]
+Name=Krecipes
+Name[sv]=Krecept
+Name[ta]=கிளிக்கர்
+Name[xx]=xxKrecipesxx
+Exec=krecipes %i %m -caption "%c"
+Icon=krecipes
+Type=Application
+DocPath=krecipes/krecipes.html
+GenericName=Cooking Book
+GenericName[bg]=Готварска книга
+GenericName[br]=Levr keginer
+GenericName[cs]=Kuchařka
+GenericName[da]=Kogebog
+GenericName[de]=Kochbuch
+GenericName[el]=Βιβλίο μαγειρικής
+GenericName[es]=Libro de cocina
+GenericName[et]=Kokaraamat
+GenericName[ga]=Leabhar Cócaireachta
+GenericName[gl]=Libro de Receitas
+GenericName[it]=Libro di ricette
+GenericName[ja]=料理本
+GenericName[ka]=სამზარეულო წიგნი
+GenericName[nb]=Kokebok
+GenericName[nl]=Kookboek
+GenericName[pa]=ਖਾਣਾ ਬਣਾਉਦ ਦੀ ਕਿਤਾਬ
+GenericName[pt]=Livro de Culinária
+GenericName[pt_BR]=Livro de Culinária
+GenericName[sr]=Кувар
+GenericName[sr@Latn]=Kuvar
+GenericName[sv]=Kokbok
+GenericName[uk]=Куховарська книга
+GenericName[xx]=xxCooking Bookxx
+GenericName[zh_CN]=食谱
+Comment=The KDE Cooking Book
+Comment[bg]=Готварската книга на KDE
+Comment[br]=Al levr keginer KDE
+Comment[bs]=KDE kuhar
+Comment[cs]=Kuchařka pro KDE
+Comment[da]=KDE's kogebog
+Comment[de]=Das KDE Kochbuch
+Comment[el]=Το βιβλίο μαγειρικής του KDE
+Comment[es]=El libro de cocina de KDE
+Comment[et]=KDE kokaraamat
+Comment[fr]=Le guide de KDE
+Comment[ga]=Leabhar Cócaireachta KDE
+Comment[gl]=O Libro de Receitas de KDE
+Comment[it]=Il libro delle ricette di KDE
+Comment[ja]=KDE 料理本
+Comment[ka]=KDE-ს სამზარეულო წიგნი
+Comment[nb]=KDE kokebok
+Comment[nl]=Het KDE-kookboek
+Comment[pa]=KDE ਖਾਣਾ ਬਣਾਉਣ ਦੀ ਕਿਤਾਬ
+Comment[pt]=Livro de Culinária do KDE
+Comment[pt_BR]=Livro de Culinária do KDE
+Comment[ru]=Книга рецептов
+Comment[sr]=Кувар за KDE
+Comment[sr@Latn]=Kuvar za KDE
+Comment[sv]=KDE:s kokbok
+Comment[ta]=KDE சமைக்குக்ம் புத்தகம்
+Comment[uk]=Куховарська книга для KDE
+Comment[xx]=xxThe KDE Cooking Bookxx
+Comment[zh_CN]=KDE 食谱
+Terminal=false
+X-DCOP-ServiceType=Unique
+Categories=Utility;Database;Qt;KDE;
diff --git a/krecipes/src/krecipes.h b/krecipes/src/krecipes.h
new file mode 100644
index 0000000..db7918e
--- /dev/null
+++ b/krecipes/src/krecipes.h
@@ -0,0 +1,140 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef _KRECIPES_H_
+#define _KRECIPES_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kapplication.h>
+#include <kmainwindow.h>
+
+#include "krecipesview.h" //for KrePanel
+
+class KrecipesView;
+
+class KPrinter;
+class KToggleAction;
+class KURL;
+class KDialog;
+class ConversionDialog;
+
+/**
+ * This class serves as the main window for Krecipes. It handles the
+ * menus, toolbars, and status bars.
+ *
+ * @short Main window class
+ * @author $AUTHOR <$EMAIL>
+ * @version $APP_VERSION
+ */
+class Krecipes : public KMainWindow
+{
+ Q_OBJECT
+public:
+ /**
+ * Default Constructor
+ */
+ Krecipes();
+
+ /**
+ * Default Destructor
+ */
+ virtual ~Krecipes();
+
+
+protected:
+ /**
+ * Overridden virtuals for Qt drag 'n drop (XDND)
+ */
+ virtual void dragEnterEvent( QDragEnterEvent *event );
+
+protected:
+ /**
+ * This function is called when it is time for the app to save its
+ * properties for session management purposes.
+ */
+ void saveProperties( KConfig * );
+
+ /**
+ * This function is called when this app is restored. The KConfig
+ * object points to the session management config file that was saved
+ * with @ref saveProperties
+ */
+ void readProperties( KConfig * );
+
+ virtual bool queryClose();
+
+
+private slots:
+ void fileNew();
+ void fileOpen();
+ void fileSave();
+ void fileExport();
+ void fileToClipboard();
+ void filePrint();
+ void optionsShowToolbar();
+ void optionsShowStatusbar();
+ void optionsConfigureKeys();
+ void optionsConfigureToolbars();
+ void optionsPreferences();
+ void newToolbarConfig();
+ void import();
+ void kreDBImport();
+ void pageSetupSlot();
+ void printSetupSlot();
+ void conversionToolSlot();
+ void backupSlot();
+ void restoreSlot();
+ void mergeSimilarCategories();
+ void mergeSimilarIngredients();
+
+ /** This function is called whenever a panel is shown or hidden and then sets
+ * actions as enabled as appropriate.
+ */
+ void updateActions( KrePanel panel, bool show );
+
+ void changeStatusbar( const QString& text );
+ void changeCaption( const QString& text );
+
+private:
+ // Private methods
+ void setupAccel();
+ void setupActions();
+
+private:
+ KrecipesView *m_view;
+
+ KPrinter *m_printer;
+ KToggleAction *m_toolbarAction;
+ KToggleAction *m_statusbarAction;
+
+private:
+ // Internal variables
+ KAction *saveAction;
+ KAction *exportAction;
+ KAction *editAction;
+ KAction *printAction;
+ KAction *reloadAction;
+ KAction *copyToClipboardAction;
+
+ KDialog *parsing_file_dlg;
+ ConversionDialog *convertDialog;
+
+ QValueList<KAction*> recipe_actions;
+
+private slots:
+ void enableSaveOption( bool en = true );
+ void recipeSelected( bool );
+};
+
+#endif // _KRECIPES_H_
diff --git a/krecipes/src/krecipes.lsm b/krecipes/src/krecipes.lsm
new file mode 100644
index 0000000..72f1447
--- /dev/null
+++ b/krecipes/src/krecipes.lsm
@@ -0,0 +1,14 @@
+Begin3
+Title: Krecipes -- The KDE Cookbook
+Version: 1.0
+Entered-date:
+Description:
+Keywords: KDE Qt
+Author: Unai Garro <ugarro@users.sourceforge.net>
+Maintained-by: Jason Kivlighn <jkivlighn@gmail.com>
+Home-page:
+Alternate-site:
+Primary-site: krecipes.sourceforge.net
+Platform: Linux. Needs KDE
+Copying-policy: GPL
+End
diff --git a/krecipes/src/krecipesdbiface.h b/krecipes/src/krecipesdbiface.h
new file mode 100644
index 0000000..a0fae49
--- /dev/null
+++ b/krecipes/src/krecipesdbiface.h
@@ -0,0 +1,26 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef _KRECIPESDBIFACE_H_
+#define _KRECIPESDBIFACE_H_
+
+#include <dcopobject.h>
+
+class KrecipesDBIface : virtual public DCOPObject
+{
+ K_DCOP
+public:
+
+k_dcop:
+ virtual void emptyData() = 0;
+ virtual bool backup( const QString &filename ) = 0;
+};
+
+#endif // _KRECIPESIFACE_H_
diff --git a/krecipes/src/krecipesiface.h b/krecipes/src/krecipesiface.h
new file mode 100644
index 0000000..ebb12b1
--- /dev/null
+++ b/krecipes/src/krecipesiface.h
@@ -0,0 +1,31 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef _KRECIPESIFACE_H_
+#define _KRECIPESIFACE_H_
+
+#include <dcopobject.h>
+#include <dcopref.h>
+
+#include <qvaluelist.h>
+
+class KrecipesIface : virtual public DCOPObject
+{
+ K_DCOP
+public:
+
+k_dcop:
+ virtual DCOPRef currentDatabase() const = 0;
+ virtual void reload() = 0;
+
+ virtual void exportRecipes( const QValueList<int> &ids ) = 0;
+};
+
+#endif // _KRECIPESIFACE_H_
diff --git a/krecipes/src/krecipesui.rc b/krecipes/src/krecipesui.rc
new file mode 100644
index 0000000..6faecfe
--- /dev/null
+++ b/krecipes/src/krecipesui.rc
@@ -0,0 +1,27 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="krecipes" version="10">
+<MenuBar>
+ <Menu name="file"><text>&amp;File</text>
+ <Action name="reload_action" />
+ <Separator />
+ <Action name="import_action" />
+ <Action name="import_db_action" />
+ <Action name="export_action" />
+ <Separator />
+ <Action name="backup_action" />
+ <Action name="restore_action" />
+ </Menu>
+ <Menu name="edit"><text>&amp;Edit</text>
+ <Action name="page_setup_action" />
+ <Action name="print_setup_action" />
+ <Separator />
+ <Action name="copy_to_clipboard_action" />
+</Menu>
+ <Menu name="tools"><text>&amp;Tools</text>
+ <Action name="edit_action" />
+ <Action name="converter_action" />
+ <Action name="merge_categories_action" />
+ <Action name="merge_ingredients_action" />
+</Menu>
+</MenuBar>
+</kpartgui>
diff --git a/krecipes/src/krecipesview.cpp b/krecipes/src/krecipesview.cpp
new file mode 100644
index 0000000..9c71b27
--- /dev/null
+++ b/krecipes/src/krecipesview.cpp
@@ -0,0 +1,939 @@
+
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "krecipesview.h"
+
+#include <cstdlib>
+
+#include <qlayout.h>
+#include <qimage.h>
+#include <qpainter.h>
+#include <qpalette.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <klibloader.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprogress.h>
+#include <krun.h>
+#include <ktrader.h>
+#include <kurl.h>
+#include <kcursor.h>
+
+#include "recipeactionshandler.h"
+#include "setupwizard.h"
+#include "kstartuplogo.h"
+
+#include "dialogs/recipeinputdialog.h"
+#include "dialogs/recipeviewdialog.h"
+#include "dialogs/selectrecipedialog.h"
+#include "dialogs/ingredientsdialog.h"
+#include "dialogs/propertiesdialog.h"
+#include "dialogs/shoppinglistdialog.h"
+#include "dialogs/dietwizarddialog.h"
+#include "dialogs/categorieseditordialog.h"
+#include "dialogs/authorsdialog.h"
+#include "dialogs/unitsdialog.h"
+#include "dialogs/prepmethodsdialog.h"
+#include "dialogs/ingredientmatcherdialog.h"
+
+#include "widgets/kremenu.h"
+#include "widgets/paneldeco.h"
+
+#include "backends/progressinterface.h"
+
+#include "profiling.h"
+
+KrecipesView::KrecipesView( QWidget *parent )
+ : DCOPObject( "KrecipesInterface" ), QVBox( parent )
+{
+ #ifndef NDEBUG
+ QTime dbg_total_timer; dbg_total_timer.start();
+ #endif
+
+ kapp->dcopClient()->setDefaultObject( objId() );
+
+ // Init the setup wizard if necessary
+ kdDebug() << "Beginning wizard" << endl;
+ wizard();
+ kdDebug() << "Wizard finished correctly" << endl;
+
+ // Show Splash Screen
+
+ KStartupLogo* start_logo = 0L;
+ start_logo = new KStartupLogo();
+ start_logo -> setHideEnabled( true );
+ start_logo->show();
+ start_logo->raise();
+
+ // Initialize Database
+
+ // Read the database setup
+
+ KConfig *config;
+ config = kapp->config();
+ config->sync();
+
+
+ // Check if the database type is among those supported
+ // and initialize the database in each case
+ START_TIMER("Initializing database")
+ initDatabase( config );
+ END_TIMER()
+
+
+ // Design the GUI
+ splitter = new QHBox( this );
+
+ // Create Left and Right Panels (splitter)
+
+
+ KIconLoader il;
+ leftPanel = new KreMenu( splitter, "leftPanel" );
+ rightPanel = new PanelDeco( splitter, "rightPanel", i18n( "Find/Edit Recipes" ), "filefind" );
+
+ // Design Left Panel
+
+ START_TIMER("Setting up buttons")
+ // Buttons
+ buttonsList = new QPtrList<KreMenuButton>();
+ buttonsList->setAutoDelete( TRUE );
+
+ button0 = new KreMenuButton( leftPanel, SelectP );
+ button0->setIconSet( il.loadIconSet( "filefind", KIcon::Panel, 32 ) );
+ buttonsList->append( button0 );
+
+ button1 = new KreMenuButton( leftPanel, ShoppingP );
+ button1->setIconSet( il.loadIconSet( "trolley", KIcon::Panel, 32 ) );
+ buttonsList->append( button1 );
+
+ button7 = new KreMenuButton( leftPanel, DietP );
+ button7->setIconSet( il.loadIconSet( "diet", KIcon::Panel, 32 ) );
+ buttonsList->append( button7 );
+
+ button8 = new KreMenuButton( leftPanel, MatcherP );
+ button8->setIconSet( il.loadIconSet( "categories", KIcon::Panel, 32 ) );
+ buttonsList->append( button8 );
+
+
+ // Submenus
+ dataMenu = leftPanel->createSubMenu( i18n( "Data" ), "2rightarrow" );
+
+ button2 = new KreMenuButton( leftPanel, IngredientsP, dataMenu );
+ button2->setIconSet( il.loadIconSet( "ingredients", KIcon::Panel, 32 ) );
+ //buttonsList->append(button2);
+
+ button3 = new KreMenuButton( leftPanel, PropertiesP, dataMenu );
+ button3->setIconSet( il.loadIconSet( "properties", KIcon::Panel, 32 ) );
+ buttonsList->append( button3 );
+
+ button4 = new KreMenuButton( leftPanel, UnitsP, dataMenu );
+ button4->setIconSet( il.loadIconSet( "units", KIcon::Panel, 32 ) );
+ buttonsList->append( button4 );
+
+ button9 = new KreMenuButton( leftPanel, PrepMethodsP, dataMenu );
+ button9->setIconSet( il.loadIconSet( "ICON PLEASE", KIcon::Panel, 32 ) );
+ buttonsList->append( button9 );
+
+ button5 = new KreMenuButton( leftPanel, CategoriesP, dataMenu );
+ button5->setIconSet( il.loadIconSet( "categories", KIcon::Panel, 32 ) );
+ buttonsList->append( button5 );
+
+ button6 = new KreMenuButton( leftPanel, AuthorsP, dataMenu );
+ button6->setIconSet( il.loadIconSet( "personal", KIcon::Panel, 32 ) );
+ buttonsList->append( button6 );
+
+ contextButton = new QPushButton( leftPanel, "contextButton" );
+ contextButton->setIconSet( il.loadIconSet( "krectip", KIcon::Panel, 32 ) );
+ contextButton->setGeometry( leftPanel->width() - 42, leftPanel->height() - 42, 32, 32 );
+ contextButton->setPaletteBackgroundColor( contextButton->paletteBackgroundColor().light( 140 ) );
+ contextButton->setFlat( true );
+ END_TIMER()
+
+ config->setGroup( "Performance" );
+ int limit = config->readNumEntry( "CategoryLimit", -1 );
+ database->updateCategoryCache(limit);
+
+ // Right Panel Widgets
+ START_TIMER("Creating input dialog")
+ inputPanel = new RecipeInputDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating recipe view")
+ viewPanel = new RecipeViewDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating recipe selection dialog")
+ selectPanel = new SelectRecipeDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating ingredients component")
+ ingredientsPanel = new IngredientsDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating properties component")
+ propertiesPanel = new PropertiesDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating units component")
+ unitsPanel = new UnitsDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating shopping list dialog")
+ shoppingListPanel = new ShoppingListDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating diet wizard dialog")
+ dietPanel = new DietWizardDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating categories component")
+ categoriesPanel = new CategoriesEditorDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating authors component")
+ authorsPanel = new AuthorsDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating prep methods component")
+ prepMethodsPanel = new PrepMethodsDialog( rightPanel, database );
+ END_TIMER()
+
+ START_TIMER("Creating ingredients matcher dialog")
+ ingredientMatcherPanel = new IngredientMatcherDialog( rightPanel, database );
+ END_TIMER()
+
+ database->clearCategoryCache();
+
+ // Use to keep track of the panels
+ panelMap.insert( inputPanel, RecipeEdit );
+ panelMap.insert( viewPanel, RecipeView );
+ panelMap.insert( selectPanel, SelectP );
+ panelMap.insert( ingredientsPanel, IngredientsP );
+ panelMap.insert( propertiesPanel, PropertiesP );
+ panelMap.insert( unitsPanel, UnitsP );
+ panelMap.insert( shoppingListPanel, ShoppingP );
+ panelMap.insert( dietPanel, DietP );
+ panelMap.insert( categoriesPanel, CategoriesP );
+ panelMap.insert( authorsPanel, AuthorsP );
+ panelMap.insert( prepMethodsPanel, PrepMethodsP );
+ panelMap.insert( ingredientMatcherPanel, MatcherP );
+
+ m_activePanel = RecipeEdit;
+
+ // i18n
+ translate();
+
+ // Initialize Variables
+ recipeButton = 0;
+
+
+
+ // Connect Signals from Left Panel to slotSetPanel()
+ connect( leftPanel, SIGNAL( clicked( KrePanel ) ), this, SLOT( slotSetPanel( KrePanel ) ) );
+
+ connect( contextButton, SIGNAL( clicked() ), SLOT( activateContextHelp() ) );
+
+ connect( leftPanel, SIGNAL( resized( int, int ) ), this, SLOT( resizeRightPane( int, int ) ) );
+
+
+ // Retransmit signal to parent to Enable/Disable the Save Button
+ connect ( inputPanel, SIGNAL( enableSaveOption( bool ) ), this, SIGNAL( enableSaveOption( bool ) ) );
+
+ // Create a new button when a recipe is unsaved
+ connect ( inputPanel, SIGNAL( createButton( QWidget*, const QString & ) ), this, SLOT( addRecipeButton( QWidget*, const QString & ) ) );
+
+ // Connect Signals from selectPanel (SelectRecipeDialog)
+
+ connect ( selectPanel, SIGNAL( recipeSelected( int, int ) ), this, SLOT( actionRecipe( int, int ) ) );
+ connect ( selectPanel, SIGNAL( recipesSelected( const QValueList<int>&, int ) ), this, SLOT( actionRecipes( const QValueList<int>&, int ) ) );
+
+ // Connect Signals from ingredientMatcherPanel (IngredientMatcherDialog)
+
+ connect ( ingredientMatcherPanel, SIGNAL( recipeSelected( int, int ) ), SLOT( actionRecipe( int, int ) ) );
+
+ // Close a recipe when requested (just switch panels)
+ connect( inputPanel, SIGNAL( closeRecipe() ), this, SLOT( closeRecipe() ) );
+
+ // Show a recipe when requested (just switch panels)
+ connect( inputPanel, SIGNAL( showRecipe( int ) ), this, SLOT( showRecipe( int ) ) );
+
+ // Create a new shopping list when a new diet is generated and accepted
+ connect( dietPanel, SIGNAL( dietReady() ), this, SLOT( createShoppingListFromDiet() ) );
+
+ // Place the Tip Button in correct position when the left pane is resized
+ connect( leftPanel, SIGNAL( resized( int, int ) ), this, SLOT( moveTipButton( int, int ) ) );
+
+ connect( rightPanel, SIGNAL( panelRaised( QWidget*, QWidget* ) ), SLOT( panelRaised( QWidget*, QWidget* ) ) );
+
+ connect( selectPanel, SIGNAL( recipeSelected(bool) ), SIGNAL( recipeSelected(bool) ) );
+
+ // Close Splash Screen
+ delete start_logo;
+
+ #ifndef NDEBUG
+ kdDebug()<<"Total time elapsed: "<<dbg_total_timer.elapsed()/1000<<" sec"<<endl;
+ #endif
+}
+
+KrecipesView::~KrecipesView()
+{
+ delete buttonsList;
+ delete viewPanel; //manually delete viewPanel because we need to be sure it is deleted
+ //before the database is because its destructor uses 'database'
+ delete database;
+}
+
+bool KrecipesView::questionRerunWizard( const QString &message, const QString &error )
+{
+ QString yesNoMessage = message + " " + i18n( "\nWould you like to run the setup wizard again? Otherwise, the application will be closed." );
+ int answer = KMessageBox::questionYesNo( this, yesNoMessage );
+
+ if ( answer == KMessageBox::Yes )
+ wizard( true );
+ else {
+ kdError() << error << ". " << i18n( "Exiting" ) << endl;
+ kapp->exit( 1 ); exit ( 1 ); //FIXME: why doesn't kapp->exit(1) do anything?
+ return false;
+ }
+
+ return true;
+}
+
+void KrecipesView::translate()
+{
+ button0->setTitle( i18n( "Find/Edit Recipes" ) );
+ button1->setTitle( i18n( "Shopping List" ) );
+ button2->setTitle( i18n( "Ingredients" ) );
+ button3->setTitle( i18n( "Properties" ) );
+ button4->setTitle( i18n( "Units" ) );
+ button9->setTitle( i18n( "Preparation Methods" ) );
+ button5->setTitle( i18n( "Categories" ) );
+ button6->setTitle( i18n( "Authors" ) );
+ button7->setTitle( i18n( "Diet Helper" ) );
+ button8->setTitle( i18n( "Ingredient Matcher" ) );
+}
+
+void KrecipesView::print()
+{
+ viewPanel->print();
+}
+
+
+void KrecipesView::slotSetTitle( const QString& title )
+{
+ emit signalChangeCaption( title );
+}
+
+// Function to switch panels
+void KrecipesView::slotSetPanel( KrePanel p )
+{
+ m_activePanel = p;
+
+ switch ( m_activePanel ) {
+ case SelectP:
+ rightPanel->setHeader( i18n( "Find/Edit Recipes" ), "filefind" );
+ rightPanel->raise( selectPanel );
+ break;
+ case ShoppingP:
+ rightPanel->setHeader( i18n( "Shopping List" ), "trolley" );
+ rightPanel->raise( shoppingListPanel );
+ shoppingListPanel->reload( Load );
+ break;
+ case DietP:
+ rightPanel->setHeader( i18n( "Diet Helper" ), "diet" );
+ rightPanel->raise( dietPanel );
+ dietPanel->reload( Load );
+ break;
+ case MatcherP:
+ rightPanel->setHeader( i18n( "Ingredient Matcher" ), "categories" );
+ rightPanel->raise( ingredientMatcherPanel );
+ ingredientMatcherPanel->reload( Load );
+ break;
+
+ case IngredientsP:
+ rightPanel->setHeader( i18n( "Ingredients" ), "ingredients" );
+ rightPanel->raise( ingredientsPanel );
+ ingredientsPanel->reload( Load );
+ break;
+ case PropertiesP:
+ rightPanel->setHeader( i18n( "Properties" ), "properties" );
+ rightPanel->raise( propertiesPanel );
+ //propertiesPanel->reload();
+ break;
+ case UnitsP:
+ rightPanel->setHeader( i18n( "Units" ), "units" );
+ rightPanel->raise( unitsPanel );
+ unitsPanel->reload( Load );
+ break;
+ case PrepMethodsP:
+ rightPanel->setHeader( i18n( "Preparation Methods" ), "GIVE ME AN ICON :p" );
+ rightPanel->raise( prepMethodsPanel );
+ prepMethodsPanel->reload( Load );
+ break;
+ case CategoriesP:
+ rightPanel->setHeader( i18n( "Categories" ), "categories" );
+ rightPanel->raise( categoriesPanel );
+ categoriesPanel->reload( Load );
+ break;
+ case AuthorsP:
+ rightPanel->setHeader( i18n( "Authors" ), "personal" );
+ rightPanel->raise( authorsPanel );
+ authorsPanel->reload( Load );
+ break;
+ case RecipeEdit:
+ rightPanel->setHeader( i18n( "Edit Recipe" ), "edit" );
+ rightPanel->raise( inputPanel );
+ break;
+ case RecipeView:
+ rightPanel->setHeader( i18n( "View Recipe" ), "filefind" );
+ rightPanel->raise( viewPanel );
+ break;
+ }
+}
+
+bool KrecipesView::save( void )
+{
+ return inputPanel->save();
+}
+
+/*!
+ \fn KrecipesView::exportRecipe()
+ */
+void KrecipesView::exportRecipe()
+{
+ QWidget * vis_panel = rightPanel->visiblePanel();
+ if ( vis_panel == viewPanel && viewPanel->recipesLoaded() > 0 ) {
+ exportRecipes( viewPanel->currentRecipes() );
+ }
+ else if ( vis_panel == selectPanel ) {
+ selectPanel->getActionsHandler()->recipeExport();
+ }
+}
+
+void KrecipesView::exportToClipboard()
+{
+ QWidget * vis_panel = rightPanel->visiblePanel();
+ if ( vis_panel == viewPanel && viewPanel->recipesLoaded() > 0 ) {
+ QValueList<int> ids = viewPanel->currentRecipes();
+ RecipeActionsHandler::recipesToClipboard( ids, database );
+ }
+ else if ( vis_panel == selectPanel ) {
+ selectPanel->getActionsHandler()->recipesToClipboard();
+ }
+}
+
+void KrecipesView::exportRecipes( const QValueList<int> &ids )
+{
+ if ( ids.count() == 1 )
+ RecipeActionsHandler::exportRecipes( ids, i18n( "Export Recipe" ), database->recipeTitle( ids[ 0 ] ), database );
+ else
+ RecipeActionsHandler::exportRecipes( ids, i18n( "Export Recipe" ), i18n( "Recipes" ), database );
+}
+
+void KrecipesView::actionRecipe( int recipeID, int action )
+{
+ switch ( action ) {
+ case 0: //Show
+ {
+ showRecipe( recipeID );
+ break;
+ }
+ case 1: // Edit
+ {
+ if ( !inputPanel->everythingSaved() )
+ {
+ switch ( KMessageBox::questionYesNoCancel( this,
+ QString( i18n( "A recipe contains unsaved changes.\n"
+ "Do you want to save changes made to this recipe before editing another recipe?" ) ),
+ i18n( "Unsaved changes" ) ) ) {
+ case KMessageBox::Yes:
+ inputPanel->save();
+ break;
+ case KMessageBox::No:
+ break;
+ case KMessageBox::Cancel:
+ return ;
+ }
+ }
+
+ inputPanel->loadRecipe( recipeID );
+ slotSetPanel( RecipeEdit );
+ break;
+ }
+ case 2: //Remove
+ {
+ switch ( KMessageBox::questionYesNo( this,
+ QString( i18n( "Are you sure you want to permanently remove the recipe, %1?" ) ).arg(database->recipeTitle(recipeID)),
+ i18n( "Confirm remove" ) ) )
+ {
+ case KMessageBox::Yes:
+ database->removeRecipe( recipeID );
+ break;
+ case KMessageBox::No:
+ break;
+ }
+ break;
+ }
+ case 3: //Add to shopping list
+ {
+ shoppingListPanel->addRecipeToShoppingList( recipeID );
+ break;
+ }
+ }
+}
+
+void KrecipesView::actionRecipes( const QValueList<int> &ids, int action )
+{
+ if ( action == 0 ) //show
+ {
+ showRecipes( ids );
+ }
+}
+
+
+void KrecipesView::createNewRecipe( void )
+{
+ if ( !inputPanel->everythingSaved() ) {
+ switch ( KMessageBox::questionYesNoCancel( this,
+ QString( i18n( "A recipe contains unsaved changes.\n"
+ "Do you want to save changes made to this recipe before creating a new recipe?" ) ),
+ i18n( "Unsaved changes" ) ) ) {
+ case KMessageBox::Yes:
+ inputPanel->save();
+ break;
+ case KMessageBox::No:
+ break;
+ case KMessageBox::Cancel:
+ return ;
+ }
+ }
+
+ inputPanel->newRecipe();
+ slotSetPanel( RecipeEdit );
+}
+
+void KrecipesView::createNewElement( void )
+{
+ //this is inconstant as the program stands...
+ /*if (rightPanel->visiblePanel())==4) //Properties Panel is the active one
+ {
+ propertiesPanel->createNewProperty();
+ }
+ else*/{
+ createNewRecipe();
+ }
+}
+
+void KrecipesView::wizard( bool force )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Wizard" );
+ bool setupDone = config->readBoolEntry( "SystemSetup", false );
+
+ QString setupVersion = config->readEntry( "Version", "0.3" ); // By default assume it's 0.3. This parameter didn't exist in that version yet.
+
+ if ( !setupDone || ( setupVersion.toDouble() < 0.5 ) || force ) // The config structure changed in version 0.4 to have DBType and Config Structure version
+ {
+
+ bool setupUser, initData, doUSDAImport, adminEnabled;
+ QString adminUser, adminPass, user, pass, host, client, dbName;
+ int port;
+ bool isRemote;
+
+ SetupWizard *setupWizard = new SetupWizard( this );
+ if ( setupWizard->exec() == QDialog::Accepted )
+ {
+ KConfig * config;
+ config = kapp->config();
+ config->sync();
+ config->setGroup( "DBType" );
+ dbType = config->readEntry( "Type", "SQLite" );
+
+ kdDebug() << "Setting up" << endl;
+ setupWizard->getOptions( setupUser, initData, doUSDAImport );
+
+ // Setup user if necessary
+ if ( ( dbType == "MySQL" || dbType == "PostgreSQL" ) && setupUser ) // Don't setup user if checkbox of existing user... was set
+ {
+ kdDebug() << "Setting up user\n";
+ setupWizard->getAdminInfo( adminEnabled, adminUser, adminPass, dbType );
+ setupWizard->getServerInfo( isRemote, host, client, dbName, user, pass, port );
+
+ if ( !adminEnabled ) // Use root without password
+ {
+ kdDebug() << "Using default admin\n";
+ if ( dbType == "MySQL" )
+ adminUser = "root";
+ else if ( dbType == "PostgreSQL" )
+ adminUser = "postgres";
+ adminPass = QString::null;
+ }
+ if ( !isRemote ) // Use localhost
+ {
+ kdDebug() << "Using localhost\n";
+ host = "localhost";
+ client = "localhost";
+ }
+
+ setupUserPermissions( host, client, dbName, user, pass, adminUser, adminPass, port );
+ }
+
+ // Initialize database with data if requested
+ if ( initData ) {
+ setupWizard->getServerInfo( isRemote, host, client, dbName, user, pass, port );
+ initializeData( host, dbName, user, pass, port ); // Populate data as normal user
+ }
+
+ if ( doUSDAImport ) {
+ // Open the DB first
+ setupWizard->getServerInfo( isRemote, host, client, dbName, user, pass, port ); //only used if needed by backend
+ RecipeDB *db = RecipeDB::createDatabase( dbType, host, user, pass, dbName, port, dbName );
+
+ // Import the data
+ if ( db ) {
+ db->connect();
+
+ if ( db->ok() ) {
+ ProgressInterface pi(this);
+ pi.listenOn(db);
+ db->importUSDADatabase();
+ }
+
+ //close the database whether ok() or not
+ delete db;
+ }
+ }
+
+ //we can do a faster usda import if this is done after it
+ if ( initData ) {
+ RecipeDB *db = RecipeDB::createDatabase( dbType, host, user, pass, dbName, port, dbName );
+ if ( db ) {
+ db->connect();
+
+ if ( db->ok() ) {
+ db->importSamples();
+ }
+
+ //close the database whether ok() or not
+ delete db;
+ }
+ }
+
+ }
+ delete setupWizard;
+ }
+}
+
+
+void KrecipesView::setupUserPermissions( const QString &host, const QString &client, const QString &dbName, const QString &newUser, const QString &newPass, const QString &adminUser, const QString &adminPass, int port )
+{
+ QString user = adminUser;
+ QString pass = adminPass;
+ if ( user.isNull() ) {
+ pass = QString::null;
+
+ if ( dbType == "PostgreSQL" )
+ user = "postgres";
+ else if ( dbType == "MySQL" )
+ user = "root";
+
+ kdDebug() << "Open db as " << user << ", with no password\n";
+ }
+ else
+ kdDebug() << "Open db as:" << user << ",*** with password ****\n";
+
+ RecipeDB *db = RecipeDB::createDatabase( dbType, host, user, pass, dbName, port, dbName );
+ if ( db ) {
+ db->connect(true,false);//create the database, but no tables (do that when connected as the user)
+ if ( db->ok() )
+ db->givePermissions( dbName, newUser, newPass, client ); // give permissions to the user
+ else
+ questionRerunWizard( db->err(), i18n( "Unable to setup database" ) );
+ }
+
+ delete db; //it closes the db automatically
+}
+
+
+void KrecipesView::initializeData( const QString &host, const QString &dbName, const QString &user, const QString &pass, int port )
+{
+ RecipeDB * db = RecipeDB::createDatabase( dbType, host, user, pass, dbName, port, dbName );
+ if ( !db ) {
+ kdError() << i18n( "Code error. No DB support has been included. Exiting" ) << endl;
+ kapp->exit( 1 );
+ }
+
+ db->connect();
+
+ if ( db->ok() ) {
+ db->emptyData();
+ db->initializeData();
+ }
+
+ delete db;
+}
+
+void KrecipesView::addRecipeButton( QWidget *w, const QString &title )
+{
+ recipeWidget = w;
+ KIconLoader il;
+ if ( !recipeButton ) {
+ recipeButton = new KreMenuButton( leftPanel, RecipeEdit );
+
+ recipeButton->setIconSet( il.loadIconSet( "filesave", KIcon::Small ) );
+
+ QString short_title = title.left( 20 );
+ if ( title.length() > 20 )
+ short_title.append( "..." );
+
+ recipeButton->setTitle( short_title );
+
+ buttonsList->append( recipeButton );
+ leftPanel->highlightButton( recipeButton );
+
+ connect( recipeButton, SIGNAL( clicked() ), this, SLOT( switchToRecipe() ) );
+ connect( ( RecipeInputDialog * ) w, SIGNAL( titleChanged( const QString& ) ), recipeButton, SLOT( setTitle( const QString& ) ) );
+ }
+
+}
+
+void KrecipesView::switchToRecipe( void )
+{
+ slotSetPanel( RecipeEdit );
+}
+
+void KrecipesView::closeRecipe( void )
+{
+ slotSetPanel( SelectP );
+ buttonsList->removeLast();
+ recipeButton = 0;
+}
+
+//Needed to make sure that the raise() is done after the construction of all the widgets, otherwise childEvent in the PanelDeco is called only _after_ the raise(), and can't be shown.
+
+void KrecipesView::show ( void )
+{
+ slotSetPanel( SelectP );
+ QWidget::show();
+}
+
+void KrecipesView::showRecipe( int recipeID )
+{
+ QValueList<int> ids;
+ ids << recipeID;
+ showRecipes( ids );
+}
+
+void KrecipesView::showRecipes( const QValueList<int> &recipeIDs )
+{
+ if ( viewPanel->loadRecipes( recipeIDs ) )
+ slotSetPanel( RecipeView );
+}
+
+void KrecipesView::activateContextHelp()
+{
+ switch ( m_activePanel ) {
+ case RecipeView:
+ //kapp->invokeHelp("");
+ break;
+
+ case SelectP:
+ kapp->invokeHelp("find-edit");
+ break;
+
+ case ShoppingP:
+ kapp->invokeHelp("shopping-list");
+ break;
+
+ case DietP:
+ kapp->invokeHelp("diet-helper");
+ break;
+
+ case MatcherP:
+ kapp->invokeHelp("ingredient-matcher");
+ break;
+
+ case RecipeEdit:
+ kapp->invokeHelp("enter-edit-recipes");
+ break;
+
+ case IngredientsP:
+ kapp->invokeHelp("ingredients-component");
+ break;
+
+ case PropertiesP:
+ kapp->invokeHelp("properties-component");
+ break;
+
+ case UnitsP:
+ kapp->invokeHelp("units-component");
+ break;
+
+ case PrepMethodsP:
+ kapp->invokeHelp("prep-methods");
+ break;
+
+ case CategoriesP:
+ kapp->invokeHelp("categories-component");
+ break;
+
+ case AuthorsP:
+ kapp->invokeHelp("authors-component");
+ break;
+ }
+}
+
+void KrecipesView::panelRaised( QWidget *w, QWidget *old_w )
+{
+ emit panelShown( panelMap[ old_w ], false );
+ emit panelShown( panelMap[ w ], true );
+}
+
+
+void KrecipesView::createShoppingListFromDiet( void )
+{
+ shoppingListPanel->createShopping( dietPanel->dietList() );
+ slotSetPanel( ShoppingP );
+}
+
+void KrecipesView::moveTipButton( int, int )
+{
+ contextButton->setGeometry( leftPanel->width() - 42, leftPanel->height() - 42, 32, 32 );
+}
+
+void KrecipesView::resizeRightPane( int lpw, int )
+{
+ QSize rpsize = rightPanel->size();
+ QPoint rpplace = rightPanel->pos();
+ rpsize.setWidth( width() - lpw );
+ rpplace.setX( lpw );
+ rightPanel->move( rpplace );
+ rightPanel->resize( rpsize );
+
+}
+
+
+
+void KrecipesView::initDatabase( KConfig *config )
+{
+
+ // Read the database type
+ config->sync();
+ config->setGroup( "DBType" );
+ dbType = checkCorrectDBType( config );
+
+
+
+ // Open the database
+ database = RecipeDB::createDatabase( dbType );
+ if ( !database ) {
+ // No DB type has been enabled(should not happen at all, but just in case)
+
+ kdError() << i18n( "Code error. No DB support was built in. Exiting" ) << endl;
+ kapp->exit( 1 );
+ }
+
+ database->connect();
+
+ while ( !database->ok() ) {
+ // Ask the user if he wants to rerun the wizard
+ bool rerun = questionRerunWizard( database->err(), i18n( "Unable to open database" ) );
+ if ( !rerun )
+ break;
+
+ // Reread the configuration file.
+ // The user may have changed the data and/or DB type
+
+ config->sync();
+ config->setGroup( "DBType" );
+ dbType = checkCorrectDBType( config );
+
+ delete database;
+ database = RecipeDB::createDatabase( dbType );
+ if ( database )
+ database->connect();
+ else {
+ // No DB type has been enabled (should not happen at all, but just in case)
+
+ kdError() << i18n( "Code error. No DB support was built in. Exiting" ) << endl;
+ kapp->exit( 1 );
+ break;
+ }
+ }
+ kdDebug() << i18n( "DB started correctly\n" ).latin1();
+}
+
+QString KrecipesView::checkCorrectDBType( KConfig *config )
+{
+ dbType = config->readEntry( "Type", "SQLite" );
+
+ while ( ( dbType != "SQLite" ) && ( dbType != "MySQL" ) && ( dbType != "PostgreSQL" ) ) {
+ questionRerunWizard( i18n( "The configured database type (%1) is unsupported." ).arg( dbType ), i18n( "Unsupported database type. Database must be either MySQL, SQLite, or PostgreSQL." ) );
+
+ // Read the database setup again
+
+ config = kapp->config();
+ config->sync();
+ config->setGroup( "DBType" );
+ dbType = config->readEntry( "Type", "SQLite" );
+ }
+ return ( dbType );
+}
+
+void KrecipesView::reloadDisplay()
+{
+ viewPanel->reload();
+}
+
+void KrecipesView::editRecipe()
+{
+ KrePanel vis_panel = panelMap[ rightPanel->visiblePanel() ];
+
+ switch ( vis_panel ) {
+ case RecipeView:
+ actionRecipe( viewPanel->currentRecipes() [ 0 ], 1 );
+ break;
+ case SelectP:
+ selectPanel->getActionsHandler()->edit();
+ break;
+ default:
+ break;
+ }
+}
+
+void KrecipesView::reload()
+{
+ viewPanel->reload();
+ selectPanel->reload( ForceReload );
+ shoppingListPanel->reload( ReloadIfPopulated );
+ ingredientsPanel->reload( ReloadIfPopulated );
+ propertiesPanel->reload();
+ unitsPanel->reload( ReloadIfPopulated );
+ dietPanel->reload( ReloadIfPopulated );
+ authorsPanel->reload( ReloadIfPopulated );
+ categoriesPanel->reload( ReloadIfPopulated );
+ ingredientMatcherPanel->reload( ReloadIfPopulated );
+ prepMethodsPanel->reload( ReloadIfPopulated );
+}
+
+DCOPRef KrecipesView::currentDatabase() const
+{
+ return DCOPRef(database);
+}
+
+
+#include "krecipesview.moc"
diff --git a/krecipes/src/krecipesview.h b/krecipes/src/krecipesview.h
new file mode 100644
index 0000000..9b933e1
--- /dev/null
+++ b/krecipes/src/krecipesview.h
@@ -0,0 +1,215 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro <ugarro@users.sourceforge.net> *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KRECIPESVIEW_H
+#define KRECIPESVIEW_H
+
+#include <qbitmap.h>
+#include <qbuttongroup.h>
+#include <qevent.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qtooltip.h>
+#include <qhbox.h>
+#include <qvaluelist.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+
+#include <kiconloader.h>
+#include <kpixmap.h>
+#include <kpixmapeffect.h>
+#include <kimageeffect.h>
+#include <kparts/part.h>
+#include <kstyle.h>
+#include <ktextbrowser.h>
+
+#include "importers/baseimporter.h"
+#include "backends/recipedb.h"
+#include "krecipesiface.h"
+
+class AuthorsDialog;
+class PrepMethodsDialog;
+class CategoriesEditorDialog;
+class DietWizardDialog;
+class IngredientsDialog;
+class Menu;
+class KreMenu;
+class KreMenuButton;
+class IngredientMatcherDialog;
+class PanelDeco;
+class PropertiesDialog;
+class QPainter;
+class RecipeInputDialog;
+class RecipeViewDialog;
+class SelectRecipeDialog;
+class ShoppingListDialog;
+class UnitsDialog;
+typedef QValueList <Menu>::Iterator MenuId;
+
+
+/**
+ * This is the main view class for Krecipes. Most of the non-menu,
+ * non-toolbar, and non-statusbar (e.g., non frame) GUI code should go
+ * here.
+ *
+ * This krecipes uses an HTML component as an example.
+ *
+ * @short Main view
+ * @author Unai Garro <ugarro@users.sourceforge.net>
+ * @version 0.4
+ */
+
+
+// Declarations
+
+
+
+// Some constants
+typedef enum KrePanel {SelectP = 0, ShoppingP, DietP, MatcherP, IngredientsP, PropertiesP, UnitsP, PrepMethodsP, CategoriesP, AuthorsP, RecipeEdit, RecipeView };
+
+
+// Class KrecipesView
+class KrecipesView : public QVBox, virtual public KrecipesIface
+{
+ Q_OBJECT
+public:
+ /**
+ * Default constructor
+ */
+ KrecipesView( QWidget *parent );
+
+ /**
+ * Destructor
+ */
+ virtual ~KrecipesView();
+
+ virtual DCOPRef currentDatabase() const;
+ RecipeDB *database;
+
+ /**
+ * Print this view to any medium -- paper or not
+ */
+ void print();
+
+ virtual void show ( void ); //Needed to make sure that the raise() is done after the construction of all the widgets, otherwise childEvent in the PanelDeco is called only _after_ the raise(), and can't be shown.
+
+signals:
+ /**
+ * Use this signal to change the content of the statusbar
+ */
+ void signalChangeStatusbar( const QString& text );
+
+ /**
+ * Use this signal to change the content of the caption
+ */
+ void signalChangeCaption( const QString& text );
+
+ void panelShown( KrePanel, bool );
+
+
+public:
+
+ // public widgets
+ RecipeInputDialog *inputPanel;
+ RecipeViewDialog *viewPanel;
+ SelectRecipeDialog *selectPanel;
+ IngredientsDialog *ingredientsPanel;
+ PropertiesDialog *propertiesPanel;
+ UnitsDialog* unitsPanel;
+ ShoppingListDialog* shoppingListPanel;
+ DietWizardDialog* dietPanel;
+ CategoriesEditorDialog *categoriesPanel;
+ AuthorsDialog *authorsPanel;
+ PrepMethodsDialog *prepMethodsPanel;
+ IngredientMatcherDialog *ingredientMatcherPanel;
+
+ // public methods
+ void createNewRecipe( void );
+ void createNewElement( void );
+
+ void exportRecipes( const QValueList<int> &ids );
+
+private:
+
+ // Internal methods
+ QString checkCorrectDBType( KConfig *config );
+ void initializeData( const QString &host, const QString &dbName, const QString &user, const QString &pass, int port );
+ void initDatabase( KConfig *config );
+ bool questionRerunWizard( const QString &message, const QString &error = "" );
+ void setupUserPermissions( const QString &host, const QString &client, const QString &dbName, const QString &newUser, const QString &newPass, const QString &adminUser = QString::null, const QString &adminPass = QString::null, int port = 0 );
+ void wizard( bool force = false );
+
+
+
+ // Widgets
+ QHBox *splitter;
+ KreMenu *leftPanel;
+ MenuId dataMenu;
+ PanelDeco *rightPanel;
+ QPtrList<KreMenuButton> *buttonsList;
+ KreMenuButton *button0;
+ KreMenuButton *button1;
+ KreMenuButton *button2;
+ KreMenuButton *button3;
+ KreMenuButton *button4;
+ KreMenuButton *button5;
+ KreMenuButton *button6;
+ KreMenuButton *button7;
+ KreMenuButton *button8;
+ KreMenuButton *button9;
+ QPushButton* contextButton;
+
+ KreMenuButton *recipeButton;
+ QWidget *recipeWidget;
+
+ // Internal variables
+ QString dbType;
+ KrePanel m_activePanel;
+
+ QMap<QWidget*, KrePanel> panelMap;
+
+ // i18n
+ void translate();
+
+
+signals:
+ void enableSaveOption( bool en );
+ void recipeSelected( bool );
+
+public slots:
+ bool save( void );
+ void exportRecipe();
+ void exportToClipboard();
+ void reloadDisplay();
+ virtual void reload();
+ void activateContextHelp();
+
+private slots:
+ void actionRecipe( int recipeID, int action );
+ void actionRecipes( const QValueList<int> &ids, int action );
+ void addRecipeButton( QWidget *w, const QString &title );
+ void closeRecipe( void );
+ void showRecipe( int recipeID );
+ void showRecipes( const QValueList<int> &recipeIDs );
+ void slotSetTitle( const QString& title );
+ void slotSetPanel( KrePanel );
+ void switchToRecipe( void );
+ void createShoppingListFromDiet( void );
+ void moveTipButton( int, int );
+ void resizeRightPane( int lpw, int lph );
+ void panelRaised( QWidget *w, QWidget *old_w );
+ void editRecipe();
+};
+
+
+#endif // KRECIPESVIEW_H
diff --git a/krecipes/src/krepagelayout.cpp b/krecipes/src/krepagelayout.cpp
new file mode 100644
index 0000000..ea87652
--- /dev/null
+++ b/krecipes/src/krepagelayout.cpp
@@ -0,0 +1,242 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
+ Copyright 2002, 2003 David Faure <faure@kde.org>
+ Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
+ Copyright 2005 Jason Kivlighn <jkivlighn@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+#include "krepagelayout.h"
+
+#include <klocale.h>
+#include <kprinter.h>
+#include <kdebug.h>
+#include <kglobal.h>
+
+#include <qdom.h>
+
+#include "kreunit.h"
+
+// paper formats ( mm )
+#define PG_A3_WIDTH 297.0
+#define PG_A3_HEIGHT 420.0
+#define PG_A4_WIDTH 210.0
+#define PG_A4_HEIGHT 297.0
+#define PG_A5_WIDTH 148.0
+#define PG_A5_HEIGHT 210.0
+#define PG_B5_WIDTH 182.0
+#define PG_B5_HEIGHT 257.0
+#define PG_US_LETTER_WIDTH 216.0
+#define PG_US_LETTER_HEIGHT 279.0
+#define PG_US_LEGAL_WIDTH 216.0
+#define PG_US_LEGAL_HEIGHT 356.0
+#define PG_US_EXECUTIVE_WIDTH 191.0
+#define PG_US_EXECUTIVE_HEIGHT 254.0
+
+QDomElement KoPageLayout::saveKreFormat( QDomDocument &doc ) const
+{
+ QDomElement style = doc.createElement( "page-layout-properties" );
+ style.setAttribute("page-width", ptWidth);
+ style.setAttribute("page-height", ptHeight);
+ style.setAttribute("margin-left", ptLeft);
+ style.setAttribute("margin-right", ptRight);
+ style.setAttribute("margin-top", ptTop);
+ style.setAttribute("margin-bottom", ptBottom);
+ style.setAttribute("print-orientation", (orientation == PG_LANDSCAPE ? "landscape" : "portrait"));kdDebug()<<"margin-left: "<<ptLeft<<endl;
+ return style;
+}
+
+void KoPageLayout::loadKreFormat(const QDomElement &style)
+{
+ if ( !style.isNull() )
+ {
+ ptWidth = KoUnit::parseValue(style.attribute("page-width", QString::null ) );
+ ptHeight = KoUnit::parseValue(style.attribute( "page-height", QString::null ) );
+ if (style.attribute( "print-orientation", QString::null)=="portrait")
+ orientation=PG_PORTRAIT;
+ else
+ orientation=PG_LANDSCAPE;
+ ptRight = KoUnit::parseValue( style.attribute( "margin-right", QString::null ) );
+ ptBottom = KoUnit::parseValue( style.attribute( "margin-bottom", QString::null ) );
+ ptLeft = KoUnit::parseValue( style.attribute( "margin-left", QString::null ) );
+ ptTop = KoUnit::parseValue( style.attribute( "margin-top", QString::null ) );
+ // guessFormat takes millimeters
+ if ( orientation == PG_LANDSCAPE )
+ format = KoPageFormat::guessFormat( POINT_TO_MM(ptHeight), POINT_TO_MM(ptWidth) );
+ else
+ format = KoPageFormat::guessFormat( POINT_TO_MM(ptWidth), POINT_TO_MM(ptHeight) );
+ }
+}
+
+
+KoPageLayout KoPageLayout::standardLayout()
+{
+ KoPageLayout layout;
+ layout.format = KoPageFormat::defaultFormat();
+ layout.orientation = PG_PORTRAIT;
+ layout.ptWidth = MM_TO_POINT( KoPageFormat::width( layout.format, layout.orientation ) );
+ layout.ptHeight = MM_TO_POINT( KoPageFormat::height( layout.format, layout.orientation ) );
+ layout.ptLeft = MM_TO_POINT( 20.0 );
+ layout.ptRight = MM_TO_POINT( 20.0 );
+ layout.ptTop = MM_TO_POINT( 20.0 );
+ layout.ptBottom = MM_TO_POINT( 20.0 );
+ return layout;
+}
+
+struct PageFormatInfo
+{
+ KoFormat format;
+ KPrinter::PageSize kprinter;
+ const char* shortName; // Short name
+ const char* descriptiveName; // Full name, which will be translated
+ double width; // in mm
+ double height; // in mm
+};
+
+// NOTES:
+// - the width and height of non-ISO formats are rounded
+// http://en.wikipedia.org/wiki/Paper_size can help
+// - the comments "should be..." indicates the exact values if the inch sizes would be multiplied by 25.4 mm/inch
+
+const PageFormatInfo pageFormatInfo[]=
+{
+ { PG_DIN_A3, KPrinter::A3, "A3", I18N_NOOP("ISO A3"), 297.0, 420.0 },
+ { PG_DIN_A4, KPrinter::A4, "A4", I18N_NOOP("ISO A4"), 210.0, 297.0 },
+ { PG_DIN_A5, KPrinter::A5, "A5", I18N_NOOP("ISO A5"), 148.0, 210.0 },
+ { PG_US_LETTER, KPrinter::Letter, "Letter", I18N_NOOP("US Letter"), 215.9, 279.4 },
+ { PG_US_LEGAL, KPrinter::Legal, "Legal", I18N_NOOP("US Legal"), 215.9, 355.6 },
+ { PG_SCREEN, KPrinter::A4, "Screen", I18N_NOOP("Screen"), PG_A4_HEIGHT, PG_A4_WIDTH }, // Custom, so fall back to A4
+ { PG_CUSTOM, KPrinter::A4, "Custom", I18N_NOOP("Custom"), PG_A4_WIDTH, PG_A4_HEIGHT }, // Custom, so fall back to A4
+ { PG_DIN_B5, KPrinter::B5, "B5", I18N_NOOP("ISO B5"), 182.0, 257.0 },
+ // Hmm, wikipedia says 184.15 * 266.7 for executive !
+ { PG_US_EXECUTIVE, KPrinter::Executive, "Executive", I18N_NOOP("US Executive"), 191.0, 254.0 }, // should be 190.5 mm x 254.0 mm
+ { PG_DIN_A0, KPrinter::A0, "A0", I18N_NOOP("ISO A0"), 841.0, 1189.0 },
+ { PG_DIN_A1, KPrinter::A1, "A1", I18N_NOOP("ISO A1"), 594.0, 841.0 },
+ { PG_DIN_A2, KPrinter::A2, "A2", I18N_NOOP("ISO A2"), 420.0, 594.0 },
+ { PG_DIN_A6, KPrinter::A6, "A6", I18N_NOOP("ISO A6"), 105.0, 148.0 },
+ { PG_DIN_A7, KPrinter::A7, "A7", I18N_NOOP("ISO A7"), 74.0, 105.0 },
+ { PG_DIN_A8, KPrinter::A8, "A8", I18N_NOOP("ISO A8"), 52.0, 74.0 },
+ { PG_DIN_A9, KPrinter::A9, "A9", I18N_NOOP("ISO A9"), 37.0, 52.0 },
+ { PG_DIN_B0, KPrinter::B0, "B0", I18N_NOOP("ISO B0"), 1030.0, 1456.0 },
+ { PG_DIN_B1, KPrinter::B1, "B1", I18N_NOOP("ISO B1"), 728.0, 1030.0 },
+ { PG_DIN_B10, KPrinter::B10, "B10", I18N_NOOP("ISO B10"), 32.0, 45.0 },
+ { PG_DIN_B2, KPrinter::B2, "B2", I18N_NOOP("ISO B2"), 515.0, 728.0 },
+ { PG_DIN_B3, KPrinter::B3, "B3", I18N_NOOP("ISO B3"), 364.0, 515.0 },
+ { PG_DIN_B4, KPrinter::B4, "B4", I18N_NOOP("ISO B4"), 257.0, 364.0 },
+ { PG_DIN_B6, KPrinter::B6, "B6", I18N_NOOP("ISO B6"), 128.0, 182.0 },
+ { PG_ISO_C5, KPrinter::C5E, "C5", I18N_NOOP("ISO C5"), 163.0, 229.0 }, // Some sources tells: 162 mm x 228 mm
+ { PG_US_COMM10, KPrinter::Comm10E, "Comm10", I18N_NOOP("US Common 10"), 105.0, 241.0 }, // should be 104.775 mm x 241.3 mm
+ { PG_ISO_DL, KPrinter::DLE, "DL", I18N_NOOP("ISO DL"), 110.0, 220.0 },
+ { PG_US_FOLIO, KPrinter::Folio, "Folio", I18N_NOOP("US Folio"), 210.0, 330.0 }, // should be 209.54 mm x 330.2 mm
+ { PG_US_LEDGER, KPrinter::Ledger, "Ledger", I18N_NOOP("US Ledger"), 432.0, 279.0 }, // should be 431.8 mm x 297.4 mm
+ { PG_US_TABLOID, KPrinter::Tabloid, "Tabloid", I18N_NOOP("US Tabloid"), 279.0, 432.0 } // should be 297.4 mm x 431.8 mm
+};
+
+int KoPageFormat::printerPageSize( KoFormat format )
+{
+ if ( format == PG_SCREEN )
+ {
+ kdWarning() << "You use the page layout SCREEN. Printing in DIN A4 LANDSCAPE." << endl;
+ return KPrinter::A4;
+ }
+ else if ( format == PG_CUSTOM )
+ {
+ kdWarning() << "The used page layout (CUSTOM) is not supported by KPrinter. Printing in A4." << endl;
+ return KPrinter::A4;
+ }
+ else if ( format <= PG_LAST_FORMAT )
+ return pageFormatInfo[ format ].kprinter;
+ else
+ return KPrinter::A4;
+}
+
+double KoPageFormat::width( KoFormat format, KoOrientation orientation )
+{
+ if ( orientation == PG_LANDSCAPE )
+ return height( format, PG_PORTRAIT );
+ if ( format <= PG_LAST_FORMAT )
+ return pageFormatInfo[ format ].width;
+ return PG_A4_WIDTH; // should never happen
+}
+
+double KoPageFormat::height( KoFormat format, KoOrientation orientation )
+{
+ if ( orientation == PG_LANDSCAPE )
+ return width( format, PG_PORTRAIT );
+ if ( format <= PG_LAST_FORMAT )
+ return pageFormatInfo[ format ].height;
+ return PG_A4_HEIGHT;
+}
+
+KoFormat KoPageFormat::guessFormat( double width, double height )
+{
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ // We have some tolerance. 1pt is a third of a mm, this is
+ // barely noticeable for a page size.
+ if ( i != PG_CUSTOM
+ && kAbs( width - pageFormatInfo[i].width ) < 1.0
+ && kAbs( height - pageFormatInfo[i].height ) < 1.0 )
+ return static_cast<KoFormat>(i);
+ }
+ return PG_CUSTOM;
+}
+
+QString KoPageFormat::formatString( KoFormat format )
+{
+ if ( format <= PG_LAST_FORMAT )
+ return QString::fromLatin1( pageFormatInfo[ format ].shortName );
+ return QString::fromLatin1( "A4" );
+}
+
+KoFormat KoPageFormat::formatFromString( const QString & string )
+{
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ if (string == QString::fromLatin1( pageFormatInfo[ i ].shortName ))
+ return pageFormatInfo[ i ].format;
+ }
+ // We do not know the format name, so we have a custom format
+ return PG_CUSTOM;
+}
+
+KoFormat KoPageFormat::defaultFormat()
+{
+ int kprinter = KGlobal::locale()->pageSize();
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ if ( pageFormatInfo[ i ].kprinter == kprinter )
+ return static_cast<KoFormat>(i);
+ }
+ return PG_DIN_A4;
+}
+
+QString KoPageFormat::name( KoFormat format )
+{
+ if ( format <= PG_LAST_FORMAT )
+ return i18n( pageFormatInfo[ format ].descriptiveName );
+ return i18n( pageFormatInfo[ PG_DIN_A4 ].descriptiveName );
+}
+
+QStringList KoPageFormat::allFormats()
+{
+ QStringList lst;
+ for ( int i = 0 ; i <= PG_LAST_FORMAT ; ++i )
+ {
+ lst << i18n( pageFormatInfo[ i ].descriptiveName );
+ }
+ return lst;
+}
diff --git a/krecipes/src/krepagelayout.h b/krecipes/src/krepagelayout.h
new file mode 100644
index 0000000..74ed31a
--- /dev/null
+++ b/krecipes/src/krepagelayout.h
@@ -0,0 +1,257 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
+ Copyright 2002, 2003 David Faure <faure@kde.org>
+ Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
+ Copyright 2005 Jason Kivlighn <jkivlighn@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+#ifndef KREPAGELAYOUT_H
+#define KREPAGELAYOUT_H
+
+#include <qstringlist.h>
+
+class QDomElement;
+class QDomDocument;
+
+/**
+ * @brief Represents the paper format a document shall be printed on.
+ *
+ * For compatibility reasons, and because of screen and custom,
+ * this enum doesn't map to QPrinter::PageSize but KoPageFormat::printerPageSize
+ * does the conversion.
+ *
+ * @todo convert DIN to ISO in the names
+ */
+enum KoFormat {
+ PG_DIN_A3 = 0,
+ PG_DIN_A4 = 1,
+ PG_DIN_A5 = 2,
+ PG_US_LETTER = 3,
+ PG_US_LEGAL = 4,
+ PG_SCREEN = 5,
+ PG_CUSTOM = 6,
+ PG_DIN_B5 = 7,
+ PG_US_EXECUTIVE = 8,
+ PG_DIN_A0 = 9,
+ PG_DIN_A1 = 10,
+ PG_DIN_A2 = 11,
+ PG_DIN_A6 = 12,
+ PG_DIN_A7 = 13,
+ PG_DIN_A8 = 14,
+ PG_DIN_A9 = 15,
+ PG_DIN_B0 = 16,
+ PG_DIN_B1 = 17,
+ PG_DIN_B10 = 18,
+ PG_DIN_B2 = 19,
+ PG_DIN_B3 = 20,
+ PG_DIN_B4 = 21,
+ PG_DIN_B6 = 22,
+ PG_ISO_C5 = 23,
+ PG_US_COMM10 = 24,
+ PG_ISO_DL = 25,
+ PG_US_FOLIO = 26,
+ PG_US_LEDGER = 27,
+ PG_US_TABLOID = 28,
+ // update the number below and the static arrays if you add more values to the enum
+ PG_LAST_FORMAT = PG_US_TABLOID // used by koPageLayout.cpp
+};
+
+/**
+ * Represents the orientation of a printed document.
+ */
+enum KoOrientation {
+ PG_PORTRAIT = 0,
+ PG_LANDSCAPE = 1
+};
+
+namespace KoPageFormat
+{
+ /**
+ * @brief Convert a KoFormat into a KPrinter::PageSize.
+ *
+ * If format is 'screen' it will use A4 landscape.
+ * If format is 'custom' it will use A4 portrait.
+ * (you may want to take care of those cases separately).
+ * Usually passed to KPrinter::setPageSize().
+ *
+ * @note We return int instead of the enum to avoid including kprinter.h
+ */
+ int /*KPrinter::PageSize*/ printerPageSize( KoFormat format );
+
+ /**
+ * Returns the width (in mm) for a given page format and orientation
+ * 'Custom' isn't supported by this function, obviously.
+ */
+ double width( KoFormat format, KoOrientation orientation );
+
+ /**
+ * Returns the height (in mm) for a given page format and orientation
+ * 'Custom' isn't supported by this function, obviously.
+ */
+ double height( KoFormat format, KoOrientation orientation );
+
+ /**
+ * Returns the internal name of the given page format.
+ * Use for saving.
+ */
+ QString formatString( KoFormat format );
+
+ /**
+ * Convert a format string (internal name) to a page format value.
+ * Use for loading.
+ */
+ KoFormat formatFromString( const QString & string );
+
+ /**
+ * Returns the default format (based on the KControl settings)
+ */
+ KoFormat defaultFormat();
+
+ /**
+ * Returns the translated name of the given page format.
+ * Use for showing the user.
+ */
+ QString name( KoFormat format );
+
+ /**
+ * Lists the translated names of all the available formats
+ */
+ QStringList allFormats();
+
+ /**
+ * Try to find the paper format for the given width and height (in mm).
+ * Useful to some import filters.
+ */
+ KoFormat guessFormat( double width, double height );
+}
+
+
+/**
+ * @brief Header/Footer type.
+ *
+ * @note Yes, this should have been a bitfield, but there was only 0, 2, 3 in koffice-1.0. Don't ask why.
+ * In the long run this should be replaced with a more flexible repetition/section concept.
+ */
+enum KoHFType {
+ HF_SAME = 0, ///< 0: Header/Footer is the same on all pages
+ HF_FIRST_EO_DIFF = 1, ///< 1: Header/Footer is different on first, even and odd pages (2&3)
+ HF_FIRST_DIFF = 2, ///< 2: Header/Footer for the first page differs
+ HF_EO_DIFF = 3 ///< 3: Header/Footer for even - odd pages are different
+};
+
+/**
+ * This structure defines the page layout, including
+ * its size in pt, its format (e.g. A4), orientation, unit, margins etc.
+ */
+struct KoPageLayout
+{
+ /** Page format */
+ KoFormat format;
+ /** Page orientation */
+ KoOrientation orientation;
+
+ /** Page width in pt */
+ double ptWidth;
+ /** Page height in pt */
+ double ptHeight;
+ /** Left margin in pt */
+ double ptLeft;
+ /** Right margin in pt */
+ double ptRight;
+ /** Top margin in pt */
+ double ptTop;
+ /** Bottom margin in pt */
+ double ptBottom;
+
+ bool operator==( const KoPageLayout& l ) const {
+ return ( ptWidth == l.ptWidth &&
+ ptHeight == l.ptHeight &&
+ ptLeft == l.ptLeft &&
+ ptRight == l.ptRight &&
+ ptTop == l.ptTop &&
+ ptBottom == l.ptBottom );
+ }
+ bool operator!=( const KoPageLayout& l ) const {
+ return !( (*this) == l );
+ }
+
+ /**
+ * Save this page layout to the Krecipes layout format
+ */
+ QDomElement saveKreFormat( QDomDocument &doc ) const;
+
+ /**
+ * Load this page layout from the Krecipes layout format
+ */
+ void loadKreFormat(const QDomElement &style);
+
+ /**
+ * @return a page layout with the default page size depending on the locale settings,
+ * default margins (2 cm), and portrait orientation.
+ * @since 1.4
+ */
+ static KoPageLayout standardLayout();
+};
+
+/** structure for header-footer */
+struct KoHeadFoot
+{
+ QString headLeft;
+ QString headMid;
+ QString headRight;
+ QString footLeft;
+ QString footMid;
+ QString footRight;
+};
+
+/** structure for columns */
+struct KoColumns
+{
+ int columns;
+ double ptColumnSpacing;
+ bool operator==( const KoColumns& rhs ) const {
+ return columns == rhs.columns &&
+ QABS(ptColumnSpacing - rhs.ptColumnSpacing) <= 1E-10;
+ }
+ bool operator!=( const KoColumns& rhs ) const {
+ return columns != rhs.columns ||
+ QABS(ptColumnSpacing - rhs.ptColumnSpacing) > 1E-10;
+ }
+};
+
+/** structure for KWord header-footer */
+struct KoKWHeaderFooter
+{
+ KoHFType header;
+ KoHFType footer;
+ double ptHeaderBodySpacing;
+ double ptFooterBodySpacing;
+ double ptFootNoteBodySpacing;
+ bool operator==( const KoKWHeaderFooter& rhs ) const {
+ return header == rhs.header && footer == rhs.footer &&
+ QABS(ptHeaderBodySpacing - rhs.ptHeaderBodySpacing) <= 1E-10 &&
+ QABS(ptFooterBodySpacing - rhs.ptFooterBodySpacing) <= 1E-10 &&
+ QABS(ptFootNoteBodySpacing - rhs.ptFootNoteBodySpacing) <= 1E-10;
+ }
+ bool operator!=( const KoKWHeaderFooter& rhs ) const {
+ return !( *this == rhs );
+ }
+};
+
+#endif /* KREPAGELAYOUT_H */
+
diff --git a/krecipes/src/kstartuplogo.cpp b/krecipes/src/kstartuplogo.cpp
new file mode 100755
index 0000000..3ea13fc
--- /dev/null
+++ b/krecipes/src/kstartuplogo.cpp
@@ -0,0 +1,70 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+* Based on kstartuplogo from Umbrello http://uml.sourceforge.net *
+***************************************************************************/
+
+#include "kstartuplogo.h"
+
+#include <kconfig.h>
+#include <kglobal.h>
+
+#include <qcursor.h>
+
+KStartupLogo::KStartupLogo( QWidget * parent, const char *name ) : QWidget( parent, name, WStyle_NoBorder | WStyle_Customize | WDestructiveClose ), m_bReadyToHide( false )
+{
+ QString dataDir = locate( "data", "krecipes/pics/startlogo.png" );
+ QPixmap pm( dataDir );
+ setBackgroundPixmap( pm );
+
+ resize(pm.size());
+ QRect desk = splashScreenDesktopGeometry();
+ setGeometry( ( desk.width() / 2 ) - ( width() / 2 ) + desk.left(),
+ ( desk.height() / 2 ) - ( height() / 2 ) + desk.top(),
+ width(), height() );
+}
+
+KStartupLogo::~KStartupLogo()
+{}
+
+void KStartupLogo::mousePressEvent( QMouseEvent* )
+{
+ if ( m_bReadyToHide ) {
+ hide();
+ }
+}
+
+/** This function is based on KDE's KGlobalSettings::splashScreenDesktopGeometry(),
+ * which is not available in KDE 3.1.
+ */
+QRect KStartupLogo::splashScreenDesktopGeometry() const
+{
+ QDesktopWidget *dw = QApplication::desktop();
+
+ if (dw->isVirtualDesktop()) {
+ KConfigGroup group(KGlobal::config(), "Windows");
+ int scr = group.readNumEntry("Unmanaged", -3);
+ if (group.readBoolEntry("XineramaEnabled", true) && scr != -2) {
+ if (scr == -3)
+ scr = dw->screenNumber(QCursor::pos());
+ return dw->screenGeometry(scr);
+ }
+ else {
+ return dw->geometry();
+ }
+ }
+ else {
+ return dw->geometry();
+ }
+}
+
+#include "kstartuplogo.moc"
diff --git a/krecipes/src/kstartuplogo.h b/krecipes/src/kstartuplogo.h
new file mode 100755
index 0000000..fe00118
--- /dev/null
+++ b/krecipes/src/kstartuplogo.h
@@ -0,0 +1,49 @@
+/***************************************************************************
+* Copyright (C) 2003 *
+* *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+* Based on kstartuplogo from Umbrello http://uml.sourceforge.net *
+***************************************************************************/
+
+
+#ifndef KSTARTUPLOGO_H
+#define KSTARTUPLOGO_H
+
+#include <qwidget.h>
+
+#include <kapplication.h>
+#include <kstandarddirs.h>
+
+/**
+ * Displays a startup splash screen
+ */
+class KStartupLogo : public QWidget
+{
+ Q_OBJECT
+public:
+ KStartupLogo( QWidget *parent = 0, const char *name = 0 );
+ ~KStartupLogo();
+ void setHideEnabled( bool bEnabled )
+ {
+ m_bReadyToHide = bEnabled;
+ };
+protected:
+ virtual void mousePressEvent( QMouseEvent* );
+ QRect splashScreenDesktopGeometry() const;
+ bool m_bReadyToHide;
+};
+
+#endif
+
+
+
+
+
diff --git a/krecipes/src/main.cpp b/krecipes/src/main.cpp
new file mode 100644
index 0000000..be3505e
--- /dev/null
+++ b/krecipes/src/main.cpp
@@ -0,0 +1,81 @@
+
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "krecipes.h"
+
+#include <iostream>
+
+#include <kuniqueapplication.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+
+#include "convert_sqlite3.h"
+
+static const char *description =
+ I18N_NOOP( "The KDE Cookbook" );
+
+static const char *version = "1.0-beta1";
+
+static KCmdLineOptions options[] =
+ {
+ { "convert-sqlite3", I18N_NOOP("Convert the current SQLite 2.x database to SQLite 3 and exit") , 0 },
+ { 0, 0, 0 }
+ };
+
+int main( int argc, char **argv )
+{
+ KAboutData about( "krecipes", I18N_NOOP( "Krecipes" ), version, description,
+ KAboutData::License_GPL, I18N_NOOP( "(C) 2003 Unai Garro\n(C) 2004-2006 Jason Kivlighn\n\n___________\n\n\nThis product is RecipeML compatible.\n You can get more information about this file format in:\n http://www.formatdata.com/recipeml" ), 0, 0, "jkivlighn@gmail.com" );
+ about.addAuthor( "Unai Garro", 0, "ugarro@users.sourceforge.net" );
+ about.addAuthor( "Jason Kivlighn", 0, "jkivlighn@gmail.com" );
+ about.addAuthor( "Cyril Bosselut", 0, "bosselut@b1project.com" );
+
+ about.addCredit( "Colleen Beamer", I18N_NOOP("Testing, bug reports, suggestions"), "colleen.beamer@gmail.com" );
+
+ about.setTranslator( I18N_NOOP( "INSERT YOUR NAME HERE" ), I18N_NOOP( "INSERT YOUR EMAIL ADDRESS" ) );
+ KCmdLineArgs::init( argc, argv, &about );
+ KCmdLineArgs::addCmdLineOptions( options );
+ KUniqueApplication::addCmdLineOptions();
+
+ if ( !KUniqueApplication::start() ) {
+ std::cout << "Krecipes is already running!" << std::endl;
+ return 0;
+ }
+
+ KUniqueApplication app;
+
+ // see if we are starting with session management
+ if ( app.isRestored() ) {
+ RESTORE( Krecipes );
+ }
+ else {
+ // no session.. just start up normally
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+
+ QApplication::flushX();
+
+ if ( args->isSet("convert-sqlite3") ) {
+ ConvertSQLite3();
+ return 0;
+ }
+
+ Krecipes * widget = new Krecipes;
+ app.setMainWidget( widget );
+ widget->show();
+
+ args->clear();
+ }
+
+ return app.exec();
+}
+
diff --git a/krecipes/src/mmdata.h b/krecipes/src/mmdata.h
new file mode 100644
index 0000000..c8ee398
--- /dev/null
+++ b/krecipes/src/mmdata.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef MMDATA_H
+#define MMDATA_H
+
+/** This file contains each of the Meal-Master unit abbreviations,
+ * and their cooresponding expansions.
+ */
+
+struct expand_unit_info
+{
+ const char *short_form;
+ const char *expanded_form;
+ const char *plural_expanded_form;
+};
+
+static expand_unit_info unit_info[] = {
+ {"bn", I18N_NOOP( "bunch" ), I18N_NOOP( "bunches" ) },
+ {"c" , I18N_NOOP( "cup" ), I18N_NOOP( "cups" ) },
+ {"cc", I18N_NOOP( "cubic cm" ), I18N_NOOP( "cubic cm" ) },
+ {"cg", I18N_NOOP( "centigram" ), I18N_NOOP( "centigrams" ) },
+ {"cl", I18N_NOOP( "centiliter" ), I18N_NOOP( "centiliters" ) },
+ {"cn", I18N_NOOP( "can" ), I18N_NOOP( "cans" ) },
+ {"ct", I18N_NOOP( "carton" ), I18N_NOOP( "cartons" ) },
+ {"dg", I18N_NOOP( "decigram" ), I18N_NOOP( "decigrams" ) },
+ {"dl", I18N_NOOP( "deciliter" ), I18N_NOOP( "deciliters" ) },
+ {"dr", I18N_NOOP( "drop" ), I18N_NOOP( "drops" ) },
+ {"ds", I18N_NOOP( "dash" ), I18N_NOOP( "dashes" ) },
+ {"ea", I18N_NOOP( "each" ), I18N_NOOP( "each" ) },
+ {"kg", I18N_NOOP( "kilogram" ), I18N_NOOP( "kilograms" ) },
+ {"fl", I18N_NOOP( "fluid ounce" ), I18N_NOOP( "fluid ounces" ) },
+ {"g" , I18N_NOOP( "gram" ), I18N_NOOP( "grams" ) },
+ {"ga", I18N_NOOP( "gallon" ), I18N_NOOP( "gallons" ) },
+ {"l" , I18N_NOOP( "liter" ), I18N_NOOP( "liters" ) },
+ {"lb", I18N_NOOP( "pound" ), I18N_NOOP( "pounds" ) },
+ {"lg", I18N_NOOP( "large" ), I18N_NOOP( "large" ) },
+ {"md", I18N_NOOP( "medium" ), I18N_NOOP( "medium" ) },
+ {"mg", I18N_NOOP( "milligram" ), I18N_NOOP( "milligrams" ) },
+ {"ml", I18N_NOOP( "milliliter" ), I18N_NOOP( "milliliters" ) },
+ {"pg", I18N_NOOP( "package" ), I18N_NOOP( "packages" ) },
+ {"pk", I18N_NOOP( "package" ), I18N_NOOP( "packages" ) },
+ {"pn", I18N_NOOP( "pinch" ), I18N_NOOP( "pinches" ) },
+ {"pt", I18N_NOOP( "pint" ), I18N_NOOP( "pints" ) },
+ {"oz", I18N_NOOP( "ounce" ), I18N_NOOP( "ounces" ) },
+ {"qt", I18N_NOOP( "quart" ), I18N_NOOP( "quarts" ) },
+ {"sl", I18N_NOOP( "slice" ), I18N_NOOP( "slices" ) },
+ {"sm", I18N_NOOP( "small" ), I18N_NOOP( "small" ) },
+ {"t" , I18N_NOOP( "teaspoon" ), I18N_NOOP( "teaspoons" ) },
+ {"tb", I18N_NOOP( "tablespoon" ), I18N_NOOP( "tablespoons" ) },
+ {"ts", I18N_NOOP( "teaspoon" ), I18N_NOOP( "teaspoons" ) },
+ {"T" , I18N_NOOP( "tablespoon" ), I18N_NOOP( "tablespoons" ) },
+ {"x" , I18N_NOOP( "per serving" ), I18N_NOOP( "per serving" ) },
+ {"", "", ""},
+ { 0, 0, 0 }
+ };
+
+#endif //MMDATA_H
diff --git a/krecipes/src/pref.cpp b/krecipes/src/pref.cpp
new file mode 100644
index 0000000..329e0e9
--- /dev/null
+++ b/krecipes/src/pref.cpp
@@ -0,0 +1,678 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2004-2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "pref.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qhbox.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qradiobutton.h>
+#include <qpushbutton.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qframe.h>
+#include <qcombobox.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kfiledialog.h>
+#include <knuminput.h>
+#include <klineedit.h>
+#include <kurlrequester.h>
+#include <kdebug.h>
+
+KrecipesPreferences::KrecipesPreferences( QWidget *parent )
+ : KDialogBase( IconList, i18n( "Krecipes Preferences" ),
+ Help | Ok | Cancel, Ok, parent )
+{
+ // this is the base class for your preferences dialog. it is now
+ // a TreeList dialog.. but there are a number of other
+ // possibilities (including Tab, Swallow, and just Plain)
+ QFrame * frame;
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "DBType" );
+
+ KIconLoader il;
+ frame = addPage( i18n( "Server Settings" ),
+ QString( i18n( "Database Server Options (%1)" ) ).arg( config->readEntry( "Type" ) ),
+ il.loadIcon( "network_local", KIcon::NoGroup, 32 ) );
+ QHBoxLayout* layout = new QHBoxLayout( frame );
+ m_pageServer = new ServerPrefs( frame );
+ layout->addWidget( m_pageServer );
+ m_helpMap.insert(0,"configure-server-settings");
+
+ frame = addPage( i18n( "Formatting" ), i18n( "Customize Formatting" ), il.loadIcon( "math_frac", KIcon::NoGroup, 32 ) );
+ QHBoxLayout* formatting_layout = new QHBoxLayout( frame );
+ m_pageNumbers = new NumbersPrefs( frame );
+ formatting_layout->addWidget( m_pageNumbers );
+ m_helpMap.insert(1,"custom-formatting");
+
+ frame = addPage( i18n( "Import/Export" ), i18n( "Recipe Import and Export Options" ), il.loadIcon( "down", KIcon::NoGroup, 32 ) );
+ QHBoxLayout* import_layout = new QHBoxLayout( frame );
+ m_pageImport = new ImportPrefs( frame );
+ import_layout->addWidget( m_pageImport );
+ m_helpMap.insert(2,"import-export-preference");
+
+ frame = addPage( i18n( "Performance" ), i18n( "Performance Options" ), il.loadIcon( "launch", KIcon::NoGroup, 32 ) );
+ QHBoxLayout* performance_layout = new QHBoxLayout( frame );
+ m_pagePerformance = new PerformancePrefs( frame );
+ performance_layout->addWidget( m_pagePerformance );
+ m_helpMap.insert(3,"configure-performance");
+
+ // Signals & Slots
+ connect ( this, SIGNAL( okClicked() ), this, SLOT( saveSettings() ) );
+
+}
+
+void KrecipesPreferences::slotHelp()
+{
+ kapp->invokeHelp( m_helpMap[activePageIndex()] );
+}
+
+
+MySQLServerPrefs::MySQLServerPrefs( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ layout->setSpacing( KDialog::spacingHint() );
+ layout->setMargin( 0 );
+
+ QSpacerItem* spacerTop = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerTop, 0, 1 );
+ QSpacerItem* spacerLeft = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacerLeft, 1, 0 );
+
+ QLabel* serverText = new QLabel( i18n( "Server:" ), this );
+ serverText->setFixedSize( QSize( 100, 20 ) );
+ serverText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( serverText, 1, 1 );
+
+ serverEdit = new KLineEdit( this );
+ serverEdit->setFixedSize( QSize( 120, 20 ) );
+ serverEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( serverEdit, 1, 2 );
+
+ QSpacerItem* spacerRow1 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow1, 2, 1 );
+
+ QLabel* usernameText = new QLabel( i18n( "Username:" ), this );
+ usernameText->setFixedSize( QSize( 100, 20 ) );
+ usernameText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( usernameText, 3, 1 );
+
+ usernameEdit = new KLineEdit( this );
+ usernameEdit->setFixedSize( QSize( 120, 20 ) );
+ usernameEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( usernameEdit, 3, 2 );
+
+ QSpacerItem* spacerRow2 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow2, 4, 1 );
+
+ QLabel* passwordText = new QLabel( i18n( "Password:" ), this );
+ passwordText->setFixedSize( QSize( 100, 20 ) );
+ passwordText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( passwordText, 5, 1 );
+
+ passwordEdit = new KLineEdit( this );
+ passwordEdit->setFixedSize( QSize( 120, 20 ) );
+ passwordEdit->setEchoMode( QLineEdit::Password );
+ passwordEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( passwordEdit, 5, 2 );
+
+ QSpacerItem* spacerRow3 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow3, 6, 1 );
+
+ QLabel* portText = new QLabel( i18n( "Port:" ), this );
+ portText->setFixedSize( QSize( 100, 20 ) );
+ portText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( portText, 7, 1 );
+
+ portEdit = new KIntNumInput( this );
+ portEdit->setMinValue(0);
+ portEdit->setSpecialValueText( i18n("Default") );
+ portEdit->setFixedSize( QSize( 120, 20 ) );
+ portEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( portEdit, 7, 2 );
+
+ QSpacerItem* spacerRow4 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow4, 8, 1 );
+
+ QLabel* dbNameText = new QLabel( i18n( "Database name:" ), this );
+ dbNameText->setFixedSize( QSize( 100, 20 ) );
+ dbNameText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( dbNameText, 9, 1 );
+
+ dbNameEdit = new KLineEdit( this );
+ dbNameEdit->setFixedSize( QSize( 120, 20 ) );
+ dbNameEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( dbNameEdit, 9, 2 );
+
+ QSpacerItem* spacerRow5 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( spacerRow5, 10, 1 );
+
+ // Backup options
+ QGroupBox *backupGBox = new QGroupBox( this, "backupGBox" );
+ backupGBox->setTitle( i18n( "Backup" ) );
+ backupGBox->setColumns( 2 );
+ layout->addMultiCellWidget( backupGBox, 10, 10, 1, 4 );
+
+ QLabel *dumpPathLabel = new QLabel( backupGBox );
+ dumpPathLabel->setText( QString(i18n( "Path to '%1':" )).arg("mysqldump") );
+ dumpPathRequester = new KURLRequester( backupGBox );
+ dumpPathRequester->setFilter( "mysqldump" );
+
+ QLabel *mysqlPathLabel = new QLabel( backupGBox );
+ mysqlPathLabel->setText( QString(i18n( "Path to '%1':" )).arg("mysql") );
+ mysqlPathRequester = new KURLRequester( backupGBox );
+ mysqlPathRequester->setFilter( "mysql" );
+
+
+ QSpacerItem* spacerRow6 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( spacerRow6, 11, 1 );
+ QSpacerItem* spacerRight = new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
+ layout->addItem( spacerRight, 1, 4 );
+
+ // Load & Save Current Settings
+ KConfig *config = kapp->config();
+ config->setGroup( "Server" );
+ serverEdit->setText( config->readEntry( "Host", "localhost" ) );
+ usernameEdit->setText( config->readEntry( "Username", "" ) );
+ passwordEdit->setText( config->readEntry( "Password", "" ) );
+ portEdit->setValue( config->readNumEntry( "Port", 0 ) );
+ dbNameEdit->setText( config->readEntry( "DBName", "Krecipes" ) );
+ dumpPathRequester->setURL( config->readEntry( "MySQLDumpPath", "mysqldump" ) );
+ mysqlPathRequester->setURL( config->readEntry( "MySQLPath", "mysql" ) );
+}
+
+void MySQLServerPrefs::saveOptions( void )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Server" );
+ config->writeEntry( "Host", serverEdit->text() );
+ config->writeEntry( "Username", usernameEdit->text() );
+ config->writeEntry( "Password", passwordEdit->text() );
+ config->writeEntry( "Port", portEdit->value() );
+ config->writeEntry( "DBName", dbNameEdit->text() );
+ config->writeEntry( "MySQLDumpPath", dumpPathRequester->url() );
+ config->writeEntry( "MySQLPath", mysqlPathRequester->url() );
+}
+
+
+PostgreSQLServerPrefs::PostgreSQLServerPrefs( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ layout->setSpacing( KDialog::spacingHint() );
+ layout->setMargin( 0 );
+
+ QSpacerItem* spacerTop = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerTop, 0, 1 );
+ QSpacerItem* spacerLeft = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacerLeft, 1, 0 );
+
+ QLabel* serverText = new QLabel( i18n( "Server:" ), this );
+ serverText->setFixedSize( QSize( 100, 20 ) );
+ serverText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( serverText, 1, 1 );
+
+ serverEdit = new KLineEdit( this );
+ serverEdit->setFixedSize( QSize( 120, 20 ) );
+ serverEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( serverEdit, 1, 2 );
+
+ QSpacerItem* spacerRow1 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow1, 2, 1 );
+
+ QLabel* usernameText = new QLabel( i18n( "Username:" ), this );
+ usernameText->setFixedSize( QSize( 100, 20 ) );
+ usernameText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( usernameText, 3, 1 );
+
+ usernameEdit = new KLineEdit( this );
+ usernameEdit->setFixedSize( QSize( 120, 20 ) );
+ usernameEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( usernameEdit, 3, 2 );
+
+ QSpacerItem* spacerRow2 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow2, 4, 1 );
+
+ QLabel* passwordText = new QLabel( i18n( "Password:" ), this );
+ passwordText->setFixedSize( QSize( 100, 20 ) );
+ passwordText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( passwordText, 5, 1 );
+
+ passwordEdit = new KLineEdit( this );
+ passwordEdit->setFixedSize( QSize( 120, 20 ) );
+ passwordEdit->setEchoMode( QLineEdit::Password );
+ passwordEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( passwordEdit, 5, 2 );
+
+ QSpacerItem* spacerRow3 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow3, 6, 1 );
+
+ QLabel* portText = new QLabel( i18n( "Port:" ), this );
+ portText->setFixedSize( QSize( 100, 20 ) );
+ portText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( portText, 7, 1 );
+
+ portEdit = new KIntNumInput( this );
+ portEdit->setMinValue(0);
+ portEdit->setSpecialValueText( i18n("Default") );
+ portEdit->setFixedSize( QSize( 120, 20 ) );
+ portEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( portEdit, 7, 2 );
+
+ QSpacerItem* spacerRow4 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerRow4, 8, 1 );
+
+ QLabel* dbNameText = new QLabel( i18n( "Database name:" ), this );
+ dbNameText->setFixedSize( QSize( 100, 20 ) );
+ dbNameText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( dbNameText, 9, 1 );
+
+ dbNameEdit = new KLineEdit( this );
+ dbNameEdit->setFixedSize( QSize( 120, 20 ) );
+ dbNameEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( dbNameEdit, 9, 2 );
+
+ QSpacerItem* spacerRow5 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( spacerRow5, 10, 1 );
+
+ // Backup options
+ QGroupBox *backupGBox = new QGroupBox( this, "backupGBox" );
+ backupGBox->setTitle( i18n( "Backup" ) );
+ backupGBox->setColumns( 2 );
+ layout->addMultiCellWidget( backupGBox, 10, 10, 1, 4 );
+
+ QLabel *dumpPathLabel = new QLabel( backupGBox );
+ dumpPathLabel->setText( QString(i18n( "Path to '%1':" )).arg("pg_dump") );
+ dumpPathRequester = new KURLRequester( backupGBox );
+ dumpPathRequester->setFilter( "pg_dump" );
+
+ QLabel *psqlPathLabel = new QLabel( backupGBox );
+ psqlPathLabel->setText( QString(i18n( "Path to '%1':" )).arg("psql") );
+ psqlPathRequester = new KURLRequester( backupGBox );
+ psqlPathRequester->setFilter( "psql" );
+
+
+ QSpacerItem* spacerRow6 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( spacerRow6, 11, 1 );
+ QSpacerItem* spacerRight = new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
+ layout->addItem( spacerRight, 1, 4 );
+
+ // Load & Save Current Settings
+ KConfig *config = kapp->config();
+ config->setGroup( "Server" );
+ serverEdit->setText( config->readEntry( "Host", "localhost" ) );
+ usernameEdit->setText( config->readEntry( "Username", "" ) );
+ passwordEdit->setText( config->readEntry( "Password", "" ) );
+ portEdit->setValue( config->readNumEntry( "Port", 0 ) );
+ dbNameEdit->setText( config->readEntry( "DBName", "Krecipes" ) );
+ dumpPathRequester->setURL( config->readEntry( "PgDumpPath", "pg_dump" ) );
+ psqlPathRequester->setURL( config->readEntry( "PsqlPath", "psql" ) );
+}
+
+void PostgreSQLServerPrefs::saveOptions( void )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Server" );
+ config->writeEntry( "Host", serverEdit->text() );
+ config->writeEntry( "Username", usernameEdit->text() );
+ config->writeEntry( "Password", passwordEdit->text() );
+ config->writeEntry( "Port", portEdit->value() );
+ config->writeEntry( "DBName", dbNameEdit->text() );
+ config->writeEntry( "PgDumpPath", dumpPathRequester->url() );
+ config->writeEntry( "PsqlPath", psqlPathRequester->url() );
+}
+
+
+
+SQLiteServerPrefs::SQLiteServerPrefs( QWidget *parent ) : QWidget( parent )
+{
+ QVBoxLayout * Form1Layout = new QVBoxLayout( this );
+
+ QHBox *hbox = new QHBox( this );
+ ( void ) new QLabel( i18n( "Database file:" ), hbox );
+
+ fileRequester = new KURLRequester( hbox );
+ hbox->setStretchFactor( fileRequester, 2 );
+
+ Form1Layout->addWidget( hbox );
+
+ QSpacerItem* spacerRow5 = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ Form1Layout->addItem( spacerRow5 );
+
+ QString sqliteBinary;
+ #if HAVE_SQLITE3
+ sqliteBinary = "sqlite3";
+ #elif HAVE_SQLITE
+ sqliteBinary = "sqlite";
+ #endif
+
+ // Backup options
+ QGroupBox *backupGBox = new QGroupBox( this, "backupGBox" );
+ backupGBox->setTitle( i18n( "Backup" ) );
+ backupGBox->setColumns( 2 );
+ Form1Layout->addWidget( backupGBox );
+
+ QLabel *dumpPathLabel = new QLabel( backupGBox );
+ dumpPathLabel->setText( QString(i18n( "Path to '%1':" )).arg(sqliteBinary) );
+ dumpPathRequester = new KURLRequester( backupGBox );
+ dumpPathRequester->setFilter( sqliteBinary );
+
+ // Load & Save Current Settings
+ KConfig *config = kapp->config();
+ config->setGroup( "Server" );
+ fileRequester->setURL( config->readEntry( "DBFile", locateLocal( "appdata", "krecipes.krecdb" ) ) );
+ dumpPathRequester->setURL( config->readEntry( "SQLitePath", sqliteBinary ) );
+}
+
+void SQLiteServerPrefs::saveOptions( void )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Server" );
+ config->writeEntry( "DBFile", fileRequester->url() );
+ config->writeEntry( "SQLitePath", dumpPathRequester->url() );
+}
+
+
+// Server Setttings Dialog
+ServerPrefs::ServerPrefs( QWidget *parent )
+ : QWidget( parent )
+{
+ QVBoxLayout * Form1Layout = new QVBoxLayout( this, 11, 6 );
+
+ KConfig *config = kapp->config();
+ config->setGroup( "DBType" );
+ QString DBtype = config->readEntry( "Type" );
+ if ( DBtype == "MySQL" )
+ serverWidget = new MySQLServerPrefs( this );
+ else if ( DBtype == "PostgreSQL" )
+ serverWidget = new PostgreSQLServerPrefs( this );
+ else
+ serverWidget = new SQLiteServerPrefs( this );
+
+ serverWidget->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ Form1Layout->addWidget( serverWidget );
+
+ Form1Layout->addItem( new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding ) );
+
+ wizard_button = new QCheckBox( i18n( "Re-run wizard on next startup" ), this );
+ wizard_button->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ Form1Layout->addWidget( wizard_button );
+
+ QLabel *note = new QLabel( i18n( "Note: Krecipes must be restarted for most server preferences to take effect." ), this );
+ Form1Layout->addWidget( note );
+
+ adjustSize();
+}
+
+
+void KrecipesPreferences::saveSettings( void )
+{
+ m_pageServer->saveOptions();
+ m_pageNumbers->saveOptions();
+ m_pageImport->saveOptions();
+ m_pagePerformance->saveOptions();
+}
+
+// Save Server settings
+void ServerPrefs::saveOptions( void )
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "DBType" );
+ QString DBtype = config->readEntry( "Type" );
+ if ( DBtype == "MySQL" )
+ ( ( MySQLServerPrefs* ) serverWidget ) ->saveOptions();
+ else if ( DBtype == "PostgreSQL" )
+ ( ( PostgreSQLServerPrefs* ) serverWidget ) ->saveOptions();
+ else
+ ( ( SQLiteServerPrefs* ) serverWidget ) ->saveOptions();
+
+ if ( wizard_button->isChecked() ) {
+ config->setGroup( "Wizard" );
+ config->writeEntry( "SystemSetup", false );
+ }
+}
+
+//=============Numbers Preferences Dialog================//
+NumbersPrefs::NumbersPrefs( QWidget *parent )
+ : QWidget( parent )
+{
+ Form1Layout = new QVBoxLayout( this, 11, 6 );
+
+ numberButtonGroup = new QButtonGroup( this );
+ numberButtonGroup->setColumnLayout( 0, Qt::Vertical );
+ numberButtonGroup->layout() ->setSpacing( 6 );
+ numberButtonGroup->layout() ->setMargin( 11 );
+ numberButtonGroup->resize( QSize() );
+ numberButtonGroupLayout = new QVBoxLayout( numberButtonGroup->layout() );
+ numberButtonGroupLayout->setAlignment( Qt::AlignTop );
+
+ fractionRadioButton = new QRadioButton( numberButtonGroup );
+ numberButtonGroupLayout->addWidget( fractionRadioButton );
+
+ decimalRadioButton = new QRadioButton( numberButtonGroup );
+ numberButtonGroupLayout->addWidget( decimalRadioButton );
+ Form1Layout->addWidget( numberButtonGroup );
+
+ numberButtonGroup->insert( decimalRadioButton, 0 );
+ numberButtonGroup->insert( fractionRadioButton, 1 );
+
+ //ingredient display format
+ QGroupBox *ingredientGrpBox = new QGroupBox( 2, Qt::Vertical, i18n( "Ingredients" ), this );
+
+ QHBox *ingredientBox = new QHBox( ingredientGrpBox );
+ ( void ) new QLabel( i18n( "Ingredient Format:" ), ingredientBox );
+ ingredientEdit = new KLineEdit( ingredientBox );
+ ( void ) new QLabel( i18n( "%n: Name<br>"
+ "%p: Preparation method<br>"
+ "%a: Amount<br>"
+ "%u: Unit"
+ ), ingredientGrpBox );
+
+ Form1Layout->addWidget( ingredientGrpBox );
+
+ //unit display format
+ QGroupBox *abbrevGrpBox = new QGroupBox( 1, Qt::Vertical, i18n( "Units" ), this );
+ QHBox *abbrevBox = new QHBox( abbrevGrpBox );
+ abbrevButton = new QCheckBox( i18n( "Use abbreviations" ), abbrevBox );
+ Form1Layout->addWidget( abbrevGrpBox );
+
+
+ Form1Layout->addItem( new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding ) );
+
+ adjustSize();
+
+ languageChange();
+
+ // Load Current Settings
+ KConfig *config = kapp->config();
+ config->setGroup( "Formatting" );
+
+ int button = ( config->readBoolEntry( "Fraction", false ) ) ? 1 : 0;
+ numberButtonGroup->setButton( button );
+
+ ingredientEdit->setText( config->readEntry( "Ingredient", "%n%p: %a %u" ) );
+
+ abbrevButton->setChecked( config->readBoolEntry( "AbbreviateUnits", false ) );
+}
+
+void NumbersPrefs::saveOptions()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Formatting" );
+
+ bool fraction = !numberButtonGroup->find( 0 ) ->isOn();
+ config->writeEntry( "Fraction", fraction );
+
+ config->writeEntry( "Ingredient", ingredientEdit->text() );
+
+ config->writeEntry( "AbbreviateUnits", abbrevButton->isChecked() );
+}
+
+void NumbersPrefs::languageChange()
+{
+ numberButtonGroup->setTitle( i18n( "Number Format" ) );
+ fractionRadioButton->setText( i18n( "Fraction" ) );
+ decimalRadioButton->setText( i18n( "Decimal" ) );
+}
+
+//=============Import/Export Preferences Dialog================//
+ImportPrefs::ImportPrefs( QWidget *parent )
+ : QWidget( parent )
+{
+ // Load Current Settings
+ KConfig * config = kapp->config();
+ config->setGroup( "Import" );
+
+ bool overwrite = config->readBoolEntry( "OverwriteExisting", false );
+ bool direct = config->readBoolEntry( "DirectImport", false );
+
+ Form1Layout = new QVBoxLayout( this, 11, 6 );
+
+ QGroupBox *importGroup = new QGroupBox(2,Qt::Vertical,i18n("Import"), this);
+
+ overwriteCheckbox = new QCheckBox( i18n( "Overwrite recipes with same title" ), importGroup );
+ overwriteCheckbox->setChecked( overwrite );
+ overwriteCheckbox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+
+ directImportCheckbox = new QCheckBox( i18n( "Ask which recipes to import" ), importGroup );
+ directImportCheckbox->setChecked( !direct );
+ directImportCheckbox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+
+ Form1Layout->addWidget(importGroup);
+
+ QGroupBox *exportGroup = new QGroupBox(1,Qt::Vertical,i18n("Export"), this);
+
+ QHBox *clipboardHBox = new QHBox(exportGroup);
+ clipboardHBox->setSpacing(6);
+ QLabel *clipboardLabel = new QLabel(i18n("'Copy to Clipboard' format:"),clipboardHBox);
+ clipBoardFormatComboBox = new QComboBox( clipboardHBox );
+ clipBoardFormatComboBox->insertItem(QString("%3 (*.txt)").arg(i18n("Plain Text")));
+ clipBoardFormatComboBox->insertItem("Krecipes (*.kreml)");
+ clipBoardFormatComboBox->insertItem("Meal-Master (*.mmf)");
+ clipBoardFormatComboBox->insertItem("Rezkonv (*.rk)");
+ clipBoardFormatComboBox->insertItem("RecipeML (*.xml)");
+ //clipBoardFormatComboBox->insertItem("CookML (*.cml)");
+ clipboardHBox->setStretchFactor(clipBoardFormatComboBox,1);
+
+ config->setGroup( "Export" );
+ QString clipboardFormat = config->readEntry("ClipboardFormat");
+ if ( clipboardFormat == "*.kreml" )
+ clipBoardFormatComboBox->setCurrentItem(1);
+ else if ( clipboardFormat == "*.mmf" )
+ clipBoardFormatComboBox->setCurrentItem(2);
+ else if ( clipboardFormat == "*.xml" )
+ clipBoardFormatComboBox->setCurrentItem(3);
+ else
+ clipBoardFormatComboBox->setCurrentItem(0);
+
+ Form1Layout->addWidget(exportGroup);
+
+ Form1Layout->addItem( new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding ) );
+
+ QWhatsThis::add( directImportCheckbox,
+ i18n("When this is enabled, the importer will show every recipe in the file(s) and allow you to select which recipes you want imported.\n \
+ \
+ Disable this to always import every recipe, which allows for faster and less memory-intensive imports.")
+ );
+
+ adjustSize();
+}
+
+void ImportPrefs::saveOptions()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Import" );
+
+ config->writeEntry( "OverwriteExisting", overwriteCheckbox->isChecked() );
+ config->writeEntry( "DirectImport", !directImportCheckbox->isChecked() );
+
+ config->setGroup( "Export" );
+ QString ext = clipBoardFormatComboBox->currentText().mid(clipBoardFormatComboBox->currentText().find("(")+1,clipBoardFormatComboBox->currentText().length()-clipBoardFormatComboBox->currentText().find("(")-2);
+ config->writeEntry( "ClipboardFormat", ext );
+}
+
+
+//=============Performance Options Dialog================//
+PerformancePrefs::PerformancePrefs( QWidget *parent )
+ : QWidget( parent )
+{
+ // Load Current Settings
+ KConfig * config = kapp->config();
+ config->setGroup( "Performance" );
+
+ int cat_limit = config->readNumEntry( "CategoryLimit", -1 );
+ int limit = config->readNumEntry( "Limit", -1 );
+
+ Form1Layout = new QVBoxLayout( this, 11, 6 );
+
+ searchAsYouTypeBox = new QCheckBox( i18n( "Search as you type" ), this );
+ searchAsYouTypeBox->setChecked( config->readBoolEntry( "SearchAsYouType", true ) );
+
+ QLabel *explainationLabel = new QLabel( i18n("In most instances these options do not need to be changed. However, limiting the amount of items displayed at once will <b>allow Krecipes to better perform when the database is loaded with many thousands of recipes</b>."), this );
+ explainationLabel->setTextFormat( Qt::RichText );
+
+ QHBox *catLimitHBox = new QHBox( this );
+ catLimitInput = new KIntNumInput(catLimitHBox);
+ catLimitInput->setLabel( i18n( "Number of categories to display at once:" ) );
+ catLimitInput->setRange(0,5000,20,true);
+ catLimitInput->setSpecialValueText( i18n("Unlimited") );
+
+ if ( cat_limit > 0 )
+ catLimitInput->setValue( cat_limit );
+
+ QHBox *limitHBox = new QHBox( this );
+ limitInput = new KIntNumInput(limitHBox);
+ limitInput->setLabel( i18n( "Number of elements to display at once:" ) );
+ limitInput->setRange(0,100000,1000,true);
+ limitInput->setSpecialValueText( i18n("Unlimited") );
+
+ if ( limit > 0 )
+ limitInput->setValue( limit );
+
+ Form1Layout->addWidget( searchAsYouTypeBox );
+ Form1Layout->addWidget( explainationLabel );
+ Form1Layout->addWidget( catLimitHBox );
+ Form1Layout->addWidget( limitHBox );
+
+ Form1Layout->addItem( new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding ) );
+
+ adjustSize();
+}
+
+void PerformancePrefs::saveOptions()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Performance" );
+
+ int catLimit = ( catLimitInput->value() == 0 ) ? -1 : catLimitInput->value();
+ config->writeEntry( "CategoryLimit", catLimit );
+
+ int limit = ( limitInput->value() == 0 ) ? -1 : limitInput->value();
+ config->writeEntry( "Limit", limit );
+
+ config->writeEntry( "SearchAsYouType", searchAsYouTypeBox->isChecked() );
+}
+
+#include "pref.moc"
diff --git a/krecipes/src/pref.h b/krecipes/src/pref.h
new file mode 100644
index 0000000..7d464f6
--- /dev/null
+++ b/krecipes/src/pref.h
@@ -0,0 +1,182 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef _KRECIPESPREF_H_
+#define _KRECIPESPREF_H_
+
+#include <kdialogbase.h>
+
+#include <qmap.h>
+
+class ServerPrefs;
+class NumbersPrefs;
+class ImportPrefs;
+class PerformancePrefs;
+
+class KIntNumInput;
+class KLineEdit;
+class KURLRequester;
+
+class QButtonGroup;
+class QCheckBox;
+class QRadioButton;
+class QVBoxLayout;
+class QComboBox;
+
+class KrecipesPreferences : public KDialogBase
+{
+ Q_OBJECT
+public:
+ KrecipesPreferences( QWidget *parent );
+
+protected slots:
+ void slotHelp();
+
+private:
+ ServerPrefs *m_pageServer;
+ NumbersPrefs *m_pageNumbers;
+ ImportPrefs *m_pageImport;
+ PerformancePrefs *m_pagePerformance;
+
+ QMap<int,QString> m_helpMap;
+
+private slots:
+ void saveSettings( void );
+};
+
+
+class MySQLServerPrefs : public QWidget
+{
+public:
+ MySQLServerPrefs( QWidget *parent );
+
+ void saveOptions( void );
+private:
+ // Internal Widgets
+ KURLRequester *dumpPathRequester;
+ KURLRequester *mysqlPathRequester;
+
+ KLineEdit *serverEdit;
+ KLineEdit *usernameEdit;
+ KLineEdit *passwordEdit;
+ KLineEdit *dbNameEdit;
+ KIntNumInput *portEdit;
+};
+
+class PostgreSQLServerPrefs : public QWidget
+{
+public:
+ PostgreSQLServerPrefs( QWidget *parent );
+
+ void saveOptions( void );
+private:
+ // Internal Widgets
+ KURLRequester *dumpPathRequester;
+ KURLRequester *psqlPathRequester;
+
+ KLineEdit *serverEdit;
+ KLineEdit *usernameEdit;
+ KLineEdit *passwordEdit;
+ KLineEdit *dbNameEdit;
+ KIntNumInput *portEdit;
+};
+
+class SQLiteServerPrefs : public QWidget
+{
+ Q_OBJECT
+
+public:
+ SQLiteServerPrefs( QWidget *parent );
+
+ void saveOptions( void );
+
+private:
+ // Internal Widgets
+ KURLRequester *dumpPathRequester;
+ KURLRequester *fileRequester;
+};
+
+
+class ServerPrefs : public QWidget
+{
+ Q_OBJECT
+public:
+ ServerPrefs( QWidget *parent = 0 );
+
+ // Public Methods
+ void saveOptions( void );
+private:
+ QWidget *serverWidget;
+ QCheckBox *wizard_button;
+};
+
+class NumbersPrefs : public QWidget
+{
+ Q_OBJECT
+
+public:
+ NumbersPrefs( QWidget *parent = 0 );
+
+ void saveOptions();
+
+protected:
+ QButtonGroup* numberButtonGroup;
+ QRadioButton* fractionRadioButton;
+ QRadioButton* decimalRadioButton;
+
+ QVBoxLayout* Form1Layout;
+ QVBoxLayout* numberButtonGroupLayout;
+
+ KLineEdit *ingredientEdit;
+ QCheckBox *abbrevButton;
+
+protected slots:
+ virtual void languageChange();
+};
+
+class ImportPrefs : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ImportPrefs( QWidget *parent = 0 );
+
+ void saveOptions();
+
+protected:
+ QVBoxLayout* Form1Layout;
+ QCheckBox* overwriteCheckbox;
+ QCheckBox* directImportCheckbox;
+
+ QComboBox *clipBoardFormatComboBox;
+};
+
+
+class PerformancePrefs : public QWidget
+{
+ Q_OBJECT
+
+public:
+ PerformancePrefs( QWidget *parent = 0 );
+
+ void saveOptions();
+
+protected:
+ QVBoxLayout* Form1Layout;
+ QCheckBox* searchAsYouTypeBox;
+ KIntNumInput* catLimitInput;
+ KIntNumInput* limitInput;
+};
+
+#endif // _KRECIPESPREF_H_
diff --git a/krecipes/src/profiling.h b/krecipes/src/profiling.h
new file mode 100644
index 0000000..d12ca4d
--- /dev/null
+++ b/krecipes/src/profiling.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PROFILING_H
+#define PROFILING_H
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#define KRECIPES_PROFILING
+
+#ifdef KRECIPES_PROFILING
+ #include <qdatetime.h>
+ #include <kdebug.h>
+ static QTime dbg_timer;
+
+ #define START_TIMER(MSG) \
+ dbg_timer.start(); kdDebug()<<MSG<<endl;
+ #define END_TIMER() \
+ kdDebug()<<"...took "<<dbg_timer.elapsed()<<" ms"<<endl;
+#else
+ #define START_TIMER(MSG)
+ #define END_TIMER()
+#endif
+
+#endif //PROFILING_H
diff --git a/krecipes/src/propertycalculator.cpp b/krecipes/src/propertycalculator.cpp
new file mode 100644
index 0000000..3b4285e
--- /dev/null
+++ b/krecipes/src/propertycalculator.cpp
@@ -0,0 +1,131 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "propertycalculator.h"
+
+#include <math.h> // For fabs()
+
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientpropertylist.h"
+#include "datablocks/recipe.h"
+
+bool autoConvert( RecipeDB* database, const Ingredient &from, const Ingredient &to, Ingredient &result )
+{
+ RecipeDB::ConversionStatus status = database->convertIngredientUnits( from, to.units, result );
+ bool converted = status == RecipeDB::Success || status == RecipeDB::MismatchedPrepMethodUsingApprox;
+
+ if ( converted ) // There is a ratio
+ {
+ double ratio = result.amount / from.amount;
+
+ if ( ratio > 1 ) // Convert to unit 1, since unit1 is bigger
+ {
+ result.units = from.units;
+ result.amount = from.amount + to.amount / ratio;
+ }
+ else { //Convert to unit2, since unit2 is bigger (just add, units are now correct)
+ result.amount += to.amount;
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+/*
+** Version with database I/O. DB must be provided
+*/
+
+void calculateProperties( Recipe& recipe, RecipeDB* database )
+{
+ recipe.properties.clear();
+ // Note that recipePropertyList is not attached to any ingredient. It's just the total of the recipe
+ IngredientPropertyList ingredientPropertyList; // property list for each ingredient
+
+ int ingredientNo = 1;
+
+ for ( IngredientList::const_iterator ing_it = recipe.ingList.begin(); ing_it != recipe.ingList.end(); ++ing_it ) {
+ database->loadProperties( &ingredientPropertyList, ( *ing_it ).ingredientID );
+ ingredientPropertyList.divide( recipe.yield.amount ); // calculates properties per yield unit
+ addPropertyToList( database, &recipe.properties, ingredientPropertyList, *ing_it, ingredientNo );
+ ingredientNo++;
+ }
+}
+
+
+void addPropertyToList( RecipeDB *database, IngredientPropertyList *recipePropertyList, IngredientPropertyList &ingPropertyList, const Ingredient &ing, int ingredientNo )
+{
+ QMap<int,double> ratioCache; //unit->ratio
+
+ IngredientPropertyList::const_iterator prop_it;
+ for ( prop_it = ingPropertyList.begin(); prop_it != ingPropertyList.end(); ++prop_it ) {
+ // Find if property was listed before
+ int pos = recipePropertyList->findIndex( *prop_it );
+ if ( pos >= 0 ) //Exists. Add to it
+ {
+ IngredientPropertyList::iterator rec_property_it = recipePropertyList->at( pos );
+ Ingredient result;
+
+ bool converted;
+ QMap<int,double>::const_iterator cache_it = ratioCache.find((*prop_it).perUnit.id);
+ if ( cache_it == ratioCache.end() ) {
+ RecipeDB::ConversionStatus status = database->convertIngredientUnits( ing, (*prop_it).perUnit, result );
+ converted = status == RecipeDB::Success || status == RecipeDB::MismatchedPrepMethodUsingApprox;
+
+ if ( converted )
+ ratioCache.insert((*prop_it).perUnit.id,result.amount / ing.amount);
+ else
+ ratioCache.insert((*prop_it).perUnit.id,-1);
+ }
+ else {
+ result.units = (*prop_it).perUnit;
+ result.amount = ing.amount * (*cache_it);
+ converted = result.amount > 0;
+ }
+
+ if ( converted ) // Could convert units to perUnit
+ (*rec_property_it).amount += ( (*prop_it).amount ) * result.amount;
+ }
+ else // Append new property
+ {
+ IngredientProperty property;
+ property.id = (*prop_it).id;
+ property.name = (*prop_it).name;
+ property.perUnit.id = -1; // It's not per unit, it's total sum of the recipe
+ property.perUnit.name = QString::null; // "
+ property.units = (*prop_it).units;
+
+ Ingredient result;
+ bool converted;
+ QMap<int,double>::const_iterator cache_it = ratioCache.find((*prop_it).perUnit.id);
+ if ( cache_it == ratioCache.end() ) {
+ RecipeDB::ConversionStatus status = database->convertIngredientUnits( ing, (*prop_it).perUnit, result );
+ converted = status == RecipeDB::Success || status == RecipeDB::MismatchedPrepMethodUsingApprox;
+ if ( converted )
+ ratioCache.insert((*prop_it).perUnit.id,result.amount / ing.amount);
+ else
+ ratioCache.insert((*prop_it).perUnit.id,-1);
+ }
+ else {
+ result.units = (*prop_it).perUnit;
+ result.amount = ing.amount * (*cache_it);
+ converted = result.amount > 0;
+ }
+
+ if ( converted ) // Could convert units to perUnit
+ {
+ property.amount = ( (*prop_it).amount ) * result.amount;
+ recipePropertyList->append( property );
+ }
+ }
+ }
+}
diff --git a/krecipes/src/propertycalculator.h b/krecipes/src/propertycalculator.h
new file mode 100644
index 0000000..3553d52
--- /dev/null
+++ b/krecipes/src/propertycalculator.h
@@ -0,0 +1,24 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PROPERTYCALCULATOR_H
+#define PROPERTYCALCULATOR_H
+
+class RecipeDB;
+class Ingredient;
+class IngredientPropertyList;
+class Recipe;
+
+bool autoConvert( RecipeDB* database, const Ingredient &from, const Ingredient &to, Ingredient &result );
+void checkUndefined( IngredientPropertyList *recipePropertyList, IngredientPropertyList &addedPropertyList );
+void calculateProperties( Recipe& recipe, RecipeDB* database );
+void addPropertyToList( RecipeDB *database, IngredientPropertyList *recipePropertyList, IngredientPropertyList &ingPropertyList, const Ingredient &ing, int ingredientNo );
+
+#endif //PROPERTYCALCULATOR_H
diff --git a/krecipes/src/recipeactionshandler.cpp b/krecipes/src/recipeactionshandler.cpp
new file mode 100644
index 0000000..112b423
--- /dev/null
+++ b/krecipes/src/recipeactionshandler.cpp
@@ -0,0 +1,475 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipeactionshandler.h"
+
+#include <qwidget.h>
+#include <qclipboard.h>
+
+#include <kapplication.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kpopupmenu.h>
+#include <kprogress.h>
+
+#include "dialogs/selectcategoriesdialog.h"
+
+#include "exporters/cookmlexporter.h"
+#include "exporters/htmlexporter.h"
+#include "exporters/htmlbookexporter.h"
+#include "exporters/kreexporter.h"
+#include "exporters/mmfexporter.h"
+#include "exporters/recipemlexporter.h"
+#include "exporters/plaintextexporter.h"
+#include "exporters/rezkonvexporter.h"
+
+#include "widgets/recipelistview.h"
+#include "widgets/categorylistview.h"
+
+#include "backends/recipedb.h"
+
+RecipeActionsHandler::RecipeActionsHandler( KListView *_parentListView, RecipeDB *db, int actions ) : QObject( _parentListView ),
+ parentListView( _parentListView ),
+ database( db )
+{
+ KIconLoader * il = new KIconLoader;
+
+ kpop = new KPopupMenu( parentListView );
+ if ( actions & Open )
+ kpop->insertItem( il->loadIcon( "ok", KIcon::NoGroup, 16 ), i18n( "&Open" ), this, SLOT( open() ), CTRL + Key_L );
+ if ( actions & Edit )
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Edit" ), this, SLOT( edit() ), CTRL + Key_E );
+ if ( actions & Export )
+ kpop->insertItem( il->loadIcon( "fileexport", KIcon::NoGroup, 16 ), i18n( "E&xport" ), this, SLOT( recipeExport() ), 0 );
+ if ( actions & RemoveFromCategory )
+ remove_from_cat_item = kpop->insertItem( il->loadIcon( "editshred", KIcon::NoGroup, 16 ), i18n( "&Remove From Category" ), this, SLOT( removeFromCategory() ), CTRL + Key_R );
+ if ( actions & Remove )
+ kpop->insertItem( il->loadIcon( "editshred", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ if ( actions & AddToShoppingList )
+ kpop->insertItem( il->loadIcon( "trolley", KIcon::NoGroup, 16 ), i18n( "&Add to Shopping List" ), this, SLOT( addToShoppingList() ), CTRL + Key_A );
+ if ( actions & CopyToClipboard )
+ kpop->insertItem( il->loadIcon( "editcopy", KIcon::NoGroup, 16 ), i18n( "&Copy to Clipboard" ), this, SLOT( recipesToClipboard() ), CTRL + Key_C );
+
+ if ( actions & Categorize )
+ categorize_item = kpop->insertItem( il->loadIcon( "categories", KIcon::NoGroup, 16 ), i18n( "Ca&tegorize..." ), this, SLOT(categorize()), CTRL + Key_T );
+
+ kpop->polish();
+
+ catPop = new KPopupMenu( parentListView );
+ if ( actions & ExpandAll )
+ catPop->insertItem( i18n( "&Expand All" ), this, SLOT( expandAll() ), CTRL + Key_Plus );
+ if ( actions & CollapseAll )
+ catPop->insertItem( i18n( "&Collapse All" ), this, SLOT( collapseAll() ), CTRL + Key_Minus );
+ if ( actions & Export )
+ catPop->insertItem( il->loadIcon( "fileexport", KIcon::NoGroup, 16 ), i18n( "E&xport" ), this, SLOT( recipeExport() ), 0 );
+
+ catPop->polish();
+
+ delete il;
+
+ connect( parentListView, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( parentListView, SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), SLOT( open() ) );
+}
+
+void RecipeActionsHandler::exec( ItemType type, const QPoint &p )
+{
+ if ( type == Recipe ) {
+ if ( kpop->idAt( 0 ) != -1 )
+ kpop->exec( p );
+ }
+ else if ( type == Category ) {
+ if ( catPop->idAt( 0 ) != -1 )
+ catPop->exec( p );
+ }
+}
+
+void RecipeActionsHandler::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i ) { // Check if the QListViewItem actually exists
+ if ( i->rtti() == 1000 ) {
+ kpop->setItemVisible( categorize_item, i->parent() && i->parent()->rtti() == 1006 );
+ kpop->setItemVisible( remove_from_cat_item, i->parent() && i->parent()->rtti() == 1001 );
+ exec( Recipe, p );
+ }
+ else if ( i->rtti() == 1001 ) //is a category... don't pop-up for an empty category though
+ exec( Category, p );
+ }
+}
+
+QValueList<int> RecipeActionsHandler::recipeIDs( const QPtrList<QListViewItem> &items ) const
+{
+ QValueList<int> ids;
+
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ if ( item->rtti() == 1000 ) { //RecipeListItem
+ RecipeListItem * recipe_it = ( RecipeListItem* ) item;
+ if ( ids.find( recipe_it->recipeID() ) == ids.end() )
+ ids << recipe_it->recipeID();
+ }
+ else if ( item->rtti() == 1001 ) {
+ CategoryListItem *cat_it = ( CategoryListItem* ) item;
+ ElementList list;
+ database->loadRecipeList( &list, cat_it->element().id, true );
+
+ for ( ElementList::const_iterator cat_it = list.begin(); cat_it != list.end(); ++cat_it ) {
+ if ( ids.find( (*cat_it).id ) == ids.end() )
+ ids << (*cat_it).id;
+ }
+ }
+ ++it;
+ }
+
+ return ids;
+}
+
+void RecipeActionsHandler::open()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ QValueList<int> ids = recipeIDs(items);
+ if ( ids.count() == 1 )
+ emit recipeSelected(ids.first(),0);
+ else if ( ids.count() > 0 )
+ emit recipesSelected(ids,0);
+ #if 0
+ else if ( it->rtti() == 1001 && it->firstChild() ) //CategoryListItem and not empty
+ {
+ QValueList<int> ids;
+
+ //do this to only iterate over children of 'it'
+ QListViewItem *pEndItem = NULL;
+ QListViewItem *pStartItem = it;
+ do
+ {
+ if ( pStartItem->nextSibling() )
+ pEndItem = pStartItem->nextSibling();
+ else
+ pStartItem = pStartItem->parent();
+ }
+ while ( pStartItem && !pEndItem );
+
+ QListViewItemIterator iterator( it );
+ while ( iterator.current() != pEndItem )
+ {
+ if ( iterator.current() ->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current();
+ if ( ids.find( recipe_it->recipeID() ) == ids.end() ) {
+ ids.append( recipe_it->recipeID() );
+ }
+ }
+ ++iterator;
+ }
+ emit recipesSelected( ids, 0 );
+ }
+ #endif
+ }
+}
+
+void RecipeActionsHandler::categorize()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ ElementList categoryList;
+ SelectCategoriesDialog *editCategoriesDialog = new SelectCategoriesDialog( parentListView, categoryList, database );
+
+ if ( editCategoriesDialog->exec() == QDialog::Accepted ) { // user presses Ok
+ editCategoriesDialog->getSelectedCategories( &categoryList ); // get the category list chosen
+
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ if ( item->parent() != 0 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) item;
+ int recipe_id = recipe_it->recipeID();
+
+ database->categorizeRecipe( recipe_id, categoryList );
+ }
+ ++it;
+ }
+ }
+
+ delete editCategoriesDialog;
+ }
+}
+
+void RecipeActionsHandler::edit()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 1 )
+ KMessageBox::sorry( kapp->mainWidget(), i18n("Please select only one recipe."), i18n("Edit Recipe") );
+ else if ( items.count() == 1 && items.at(0)->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) items.at(0);
+ emit recipeSelected( recipe_it->recipeID(), 1 );
+ }
+ else //either nothing was selected or a category was selected
+ KMessageBox::sorry( kapp->mainWidget(), i18n("No recipes selected."), i18n("Edit Recipe") );
+}
+
+void RecipeActionsHandler::recipeExport()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ QValueList<int> ids = recipeIDs( items );
+
+ QString title;
+ if ( items.count() == 1 && items.at(0)->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) items.at(0);
+ title = recipe_it->title();
+ }
+ else
+ title = i18n( "Recipes" );
+
+ exportRecipes( ids, i18n( "Export Recipe" ), title, database );
+ }
+ else //if nothing selected, export all visible recipes
+ {
+ QValueList<int> ids = getAllVisibleItems();
+ if ( ids.count() > 0 ) {
+ switch ( KMessageBox::questionYesNo( kapp->mainWidget(), i18n("No recipes are currently selected.\nWould you like to export all recipes in the current view?")) )
+ {
+ case KMessageBox::Yes:
+ exportRecipes( ids, i18n( "Export Recipes" ), i18n( "Recipes" ), database );
+ break;
+ default: break;
+ }
+ }
+ else
+ KMessageBox::sorry( kapp->mainWidget(), i18n("No recipes selected."), i18n("Export") );
+ }
+}
+
+void RecipeActionsHandler::removeFromCategory()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ if ( item->parent() != 0 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) item;
+ int recipe_id = recipe_it->recipeID();
+
+ CategoryListItem *cat_it = ( CategoryListItem* ) item->parent();
+ database->removeRecipeFromCategory( recipe_id, cat_it->categoryId() );
+ }
+ ++it;
+ }
+ }
+}
+
+void RecipeActionsHandler::remove()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ if ( item->rtti() == RECIPELISTITEM_RTTI ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) item;
+ emit recipeSelected( recipe_it->recipeID(), 2 );
+ }
+ ++it;
+ }
+ }
+}
+
+void RecipeActionsHandler::addToShoppingList()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ QPtrListIterator<QListViewItem> it(items);
+ QListViewItem *item;
+ while ( (item = it.current()) != 0 ) {
+ if ( item->parent() != 0 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) item;
+ emit recipeSelected( recipe_it->recipeID(), 3 );
+ }
+ ++it;
+ }
+ }
+}
+
+void RecipeActionsHandler::expandAll()
+{
+ QListViewItemIterator it( parentListView );
+ while ( it.current() ) {
+ QListViewItem * item = it.current();
+ item->setOpen( true );
+ ++it;
+ }
+}
+
+void RecipeActionsHandler::collapseAll()
+{
+ QListViewItemIterator it( parentListView );
+ while ( it.current() ) {
+ QListViewItem * item = it.current();
+ item->setOpen( false );
+ ++it;
+ }
+}
+
+void RecipeActionsHandler::exportRecipe( int id, const QString & caption, const QString &selection, RecipeDB *db )
+{
+ QValueList<int> ids;
+ ids.append( id );
+
+ exportRecipes( ids, caption, selection, db );
+}
+
+void RecipeActionsHandler::exportRecipes( const QValueList<int> &ids, const QString & caption, const QString &selection, RecipeDB *database )
+{
+ KFileDialog * fd = new KFileDialog( QString::null,
+ QString( "*.kre|%1 (*.kre)\n"
+ "*.kreml|Krecipes (*.kreml)\n"
+ "*.txt|%3 (*.txt)\n"
+ //"*.cml|CookML (*.cml)\n"
+ "*|Web Book\n"
+ "*.html|%2 (*.html)\n"
+ "*.mmf|Meal-Master (*.mmf)\n"
+ "*.xml|RecipeML (*.xml)\n"
+ "*.rk|Rezkonv (*.rk)"
+ ).arg( i18n( "Compressed Krecipes format" ) ).arg( i18n( "Web page" ) ).arg( i18n("Plain Text") ),
+ 0, "export_dlg", true );
+ fd->setCaption( caption );
+ fd->setOperationMode( KFileDialog::Saving );
+ fd->setSelection( selection );
+ fd->setMode( KFile::File | KFile::Directory );
+ if ( fd->exec() == KFileDialog::Accepted ) {
+ QString fileName = fd->selectedFile();
+ if ( !fileName.isNull() ) {
+ BaseExporter * exporter;
+ if ( fd->currentFilter() == "*.xml" )
+ exporter = new RecipeMLExporter( fileName, fd->currentFilter() );
+ else if ( fd->currentFilter() == "*.mmf" )
+ exporter = new MMFExporter( fileName, fd->currentFilter() );
+ else if ( fd->currentFilter() == "*" ) {
+ CategoryTree *cat_structure = new CategoryTree;
+ database->loadCategories( cat_structure );
+ exporter = new HTMLBookExporter( cat_structure, fd->baseURL().path(), "*.html" );
+ }
+ else if ( fd->currentFilter() == "*.html" ) {
+ exporter = new HTMLExporter( fileName, fd->currentFilter() );
+ }
+ else if ( fd->currentFilter() == "*.cml" )
+ exporter = new CookMLExporter( fileName, fd->currentFilter() );
+ else if ( fd->currentFilter() == "*.txt" )
+ exporter = new PlainTextExporter( fileName, fd->currentFilter() );
+ else if ( fd->currentFilter() == "*.rk" )
+ exporter = new RezkonvExporter( fileName, fd->currentFilter() );
+ else {
+ CategoryTree *cat_structure = new CategoryTree;
+ database->loadCategories( cat_structure );
+ exporter = new KreExporter( cat_structure, fileName, fd->currentFilter() );
+ }
+
+ int overwrite = -1;
+ if ( QFile::exists( exporter->fileName() ) ) {
+ overwrite = KMessageBox::warningYesNo( 0, QString( i18n( "File \"%1\" exists. Are you sure you want to overwrite it?" ) ).arg( exporter->fileName() ), i18n( "Saving recipe" ) );
+ }
+
+ if ( overwrite == KMessageBox::Yes || overwrite == -1 ) {
+ KProgressDialog progress_dialog( 0, "export_progress_dialog", QString::null, i18n( "Saving recipes..." ) );
+ exporter->exporter( ids, database, &progress_dialog );
+ }
+ delete exporter;
+ }
+ }
+ delete fd;
+}
+
+void RecipeActionsHandler::recipesToClipboard( const QValueList<int> &ids, RecipeDB *db )
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup("Export");
+ QString formatFilter = config->readEntry("ClipboardFormat");
+
+ BaseExporter * exporter;
+ if ( formatFilter == "*.xml" )
+ exporter = new RecipeMLExporter( QString::null, formatFilter );
+ else if ( formatFilter == "*.mmf" )
+ exporter = new MMFExporter( QString::null, formatFilter );
+ else if ( formatFilter == "*.cml" )
+ exporter = new CookMLExporter( QString::null, formatFilter );
+ else if ( formatFilter == "*.rk" )
+ exporter = new RezkonvExporter( QString::null, formatFilter );
+ else if ( formatFilter == "*.kre" || formatFilter == "*.kreml" ) {
+ CategoryTree *cat_structure = new CategoryTree;
+ db->loadCategories( cat_structure );
+ exporter = new KreExporter( cat_structure, QString::null, formatFilter );
+ }
+ else //default to plain text
+ exporter = new PlainTextExporter( QString::null, "*.txt" );
+
+ RecipeList recipeList;
+ db->loadRecipes( &recipeList, exporter->supportedItems(), ids );
+
+ QString buffer;
+ QTextStream stream(buffer,IO_WriteOnly);
+ exporter->writeStream(stream,recipeList);
+
+ delete exporter;
+
+ QApplication::clipboard()->setText(buffer);
+}
+
+void RecipeActionsHandler::recipesToClipboard()
+{
+ QPtrList<QListViewItem> items = parentListView->selectedItems();
+ if ( items.count() > 0 ) {
+ QValueList<int> ids = recipeIDs( items );
+
+ recipesToClipboard(ids,database);
+ }
+}
+
+QValueList<int> RecipeActionsHandler::getAllVisibleItems()
+{
+ QValueList<int> ids;
+
+ QListViewItemIterator iterator( parentListView );
+ while ( iterator.current() ) {
+ if ( iterator.current() ->isVisible() ) {
+ if ( iterator.current() ->rtti() == RECIPELISTITEM_RTTI ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current();
+ int recipe_id = recipe_it->recipeID();
+
+ if ( ids.find( recipe_id ) == ids.end() )
+ ids.append( recipe_id );
+ }
+ //it is a category item and isn't populated, so get the unpopulated data from the database
+ else if ( iterator.current()->rtti() == CATEGORYLISTITEM_RTTI && !iterator.current()->firstChild() ) {
+ int cat_id = (( CategoryListItem* ) iterator.current())->element().id;
+ ElementList list;
+ database->loadRecipeList( &list, cat_id, true );
+
+ for ( ElementList::const_iterator it = list.begin(); it != list.end(); ++it ) {
+ if ( ids.find( (*it).id ) == ids.end() )
+ ids << (*it).id;
+ }
+ }
+ }
+
+ ++iterator;
+ }
+
+ return ids;
+}
+
+#include "recipeactionshandler.moc"
+
diff --git a/krecipes/src/recipeactionshandler.h b/krecipes/src/recipeactionshandler.h
new file mode 100644
index 0000000..eefa9bb
--- /dev/null
+++ b/krecipes/src/recipeactionshandler.h
@@ -0,0 +1,119 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEACTIONSHANDLER_H
+#define RECIPEACTIONSHANDLER_H
+
+#include <qobject.h>
+#include <qvaluelist.h>
+#include <qptrlist.h>
+
+class QListViewItem;
+class KListView;
+class KPopupMenu;
+class RecipeDB;
+
+/** @brief A class that centralizes common actions for recipes such as saving and editing.
+ *
+ * It acts upon a given KListView that is assumed to be a list of recipes. It
+ * automagically enables this list view with a popup menu for user access to
+ * the provided actions.
+ *
+ * @author Jason Kivlighn
+ */
+class RecipeActionsHandler : public QObject
+{
+ Q_OBJECT
+
+public:
+ typedef enum ItemType { Category, Recipe };
+ typedef enum RecipeActions {
+ AllActions = 0xffff,
+ Open = 0x0001,
+ Edit = 0x0002,
+ Export = 0x0004,
+ RemoveFromCategory = 0x0008,
+ Remove = 0x0010,
+ ExpandAll = 0x0020,
+ CollapseAll = 0x0040,
+ AddToShoppingList = 0x0080,
+ CopyToClipboard = 0x0100,
+ Categorize = 0x0200
+ };
+
+ RecipeActionsHandler( KListView *parentListView, RecipeDB *db, int actions = AllActions );
+ ~RecipeActionsHandler()
+ {}
+
+ static void exportRecipes( const QValueList<int> &ids, const QString & caption, const QString &selection, RecipeDB *db );
+ static void exportRecipe( int id, const QString & caption, const QString &selection, RecipeDB *db );
+ static void recipesToClipboard( const QValueList<int> &ids, RecipeDB *db );
+
+signals:
+ void recipeSelected( int id, int action );
+ void recipesSelected( const QValueList<int> &ids, int action );
+
+public slots:
+ void exec( ItemType type, const QPoint &p );
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void categorize();
+
+ /** Signals an open event (via the recipeSelected() signal) for the recipe(s) currently
+ * selected in the list view
+ */
+ void open();
+
+ /** Signals an edit event (via the recipeSelected() signal) for the recipe currently
+ * selected in the list view
+ */
+ void edit();
+
+ /** Saves the recipe(s) currently selected in the list view, prompting with a file
+ * dialog.
+ */
+ void recipeExport();
+
+ /** Removes the recipe(s) currently selected in the list view from its current category */
+ void removeFromCategory();
+
+ /** Removes the recipe(s) currently selected in the list view from the database */
+ void remove
+ ();
+
+ /** Add the recipe(s) currently selected in the list view to the shopping list dialog */
+ void addToShoppingList();
+
+ /** Expands all items in the list view */
+ void expandAll();
+
+ /** Collapses all items in the list view */
+ void collapseAll();
+
+ void recipesToClipboard();
+
+private:
+ KPopupMenu *kpop;
+ KPopupMenu *catPop;
+
+ KListView *parentListView;
+ RecipeDB *database;
+
+ int remove_from_cat_item;
+ int categorize_item;
+
+ QValueList<int> getAllVisibleItems();
+ QValueList<int> recipeIDs( const QPtrList<QListViewItem> &items ) const;
+};
+
+#endif //RECIPEACTIONSHANDLER_H
+
diff --git a/krecipes/src/recipefilter.cpp b/krecipes/src/recipefilter.cpp
new file mode 100644
index 0000000..f350edd
--- /dev/null
+++ b/krecipes/src/recipefilter.cpp
@@ -0,0 +1,154 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipefilter.h"
+
+#include <kdebug.h>
+
+#include "widgets/recipelistview.h"
+
+RecipeFilter::RecipeFilter( KListView *klv ) : QObject( klv ),
+ listview( klv ),
+ currentCategory( 0 )
+{}
+
+void RecipeFilter::filter( const QString &s )
+{
+ //do this to only iterate over children of 'currentCategory'
+ QListViewItem * pEndItem = NULL;
+ if ( currentCategory ) {
+ QListViewItem * pStartItem = currentCategory;
+ do {
+ if ( pStartItem->nextSibling() )
+ pEndItem = pStartItem->nextSibling();
+ else
+ pStartItem = pStartItem->parent();
+ }
+ while ( pStartItem && !pEndItem );
+ }
+
+ //Re-show everything
+ QListViewItemIterator list_it;
+ if ( currentCategory )
+ list_it = QListViewItemIterator( currentCategory );
+ else
+ list_it = QListViewItemIterator( listview );
+ while ( list_it.current() != pEndItem ) {
+ list_it.current() ->setVisible( true );
+ list_it++;
+ }
+
+ // Only filter if the filter text isn't empty
+ if ( !s.isEmpty() ) {
+ QListViewItemIterator list_it( listview );
+ while ( QListViewItem * it = list_it.current() ) {
+ if ( it->rtti() == 1000 ) // Its a recipe
+ {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) it;
+
+ if ( recipe_it->title().contains( s, false ) )
+ {
+ if ( currentCategory ) {
+ if ( isParentOf( currentCategory, recipe_it ) )
+ recipe_it->setVisible( true );
+ else
+ recipe_it->setVisible( false );
+ }
+ else
+ recipe_it->setVisible( true );
+ }
+ else
+ recipe_it->setVisible( false );
+ }
+
+ ++list_it;
+ }
+ hideIfEmpty();
+ }
+}
+
+void RecipeFilter::filterCategory( int categoryID )
+{
+ kdDebug() << "I got category :" << categoryID << "\n";
+
+ if ( categoryID == -1 )
+ currentCategory = 0;
+ else {
+ QListViewItemIterator list_it( listview );
+ while ( QListViewItem * it = list_it.current() ) {
+ if ( it->rtti() == 1001 ) {
+ CategoryListItem * cat_it = ( CategoryListItem* ) it;
+ if ( cat_it->categoryId() == categoryID ) {
+ currentCategory = cat_it;
+ break;
+ }
+ }
+
+ ++list_it;
+ }
+ }
+
+ QListViewItemIterator list_it( listview );
+ while ( QListViewItem * it = list_it.current() ) {
+ if ( categoryID == -1 )
+ it->setVisible( true ); // We're not filtering categories
+ else if ( it == currentCategory || isParentOf( it, currentCategory ) || isParentOf( currentCategory, it ) )
+ it->setVisible( true );
+ else
+ it->setVisible( false );
+
+ ++list_it;
+ }
+
+ if ( currentCategory )
+ currentCategory->setOpen( true );
+}
+
+bool RecipeFilter::hideIfEmpty( QListViewItem *parent )
+{
+ QListViewItem * it;
+ if ( parent == 0 )
+ it = listview->firstChild();
+ else
+ it = parent->firstChild();
+
+ bool parent_should_show = false;
+ for ( ; it; it = it->nextSibling() ) {
+ if ( (it->rtti() == 1000 && it->isVisible()) || (it->rtti() == NEXTLISTITEM_RTTI || it->rtti() == PREVLISTITEM_RTTI) ) {
+ parent_should_show = true;
+ }
+ else {
+ bool result = hideIfEmpty( it );
+ if ( parent_should_show == false )
+ parent_should_show = result;
+ }
+ }
+
+ if ( parent && parent->rtti() != 1000 ) {
+ if ( parent_should_show )
+ parent->setOpen( true );
+ parent->setVisible( parent_should_show );
+ }
+ return parent_should_show;
+}
+
+bool RecipeFilter::isParentOf( QListViewItem *parent, QListViewItem *to_check )
+{
+ for ( QListViewItem * it = to_check->parent(); it; it = it->parent() ) {
+ if ( it == parent )
+ return true;
+ }
+
+ return false;
+}
+
+#include "recipefilter.moc"
+
diff --git a/krecipes/src/recipefilter.h b/krecipes/src/recipefilter.h
new file mode 100644
index 0000000..b7f2574
--- /dev/null
+++ b/krecipes/src/recipefilter.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPEFILTER_H
+#define RECIPEFILTER_H
+
+#include <qobject.h>
+
+class KListView;
+class QListViewItem;
+
+class RecipeFilter : public QObject
+{
+ Q_OBJECT
+
+public:
+ RecipeFilter( KListView * );
+
+public slots:
+ void filter( const QString & );
+ void filterCategory( int categoryID );
+
+private:
+ bool isParentOf( QListViewItem *parent, QListViewItem *to_check );
+ bool hideIfEmpty( QListViewItem *parent = 0 );
+
+ KListView *listview;
+ QListViewItem *currentCategory;
+};
+
+#endif //RECIPEFILTER_H
diff --git a/krecipes/src/setupwizard.cpp b/krecipes/src/setupwizard.cpp
new file mode 100644
index 0000000..b268c3b
--- /dev/null
+++ b/krecipes/src/setupwizard.cpp
@@ -0,0 +1,851 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <unistd.h>
+#include <pwd.h>
+
+#include <qhbox.h>
+#include <qvgroupbox.h>
+#include <qlayout.h>
+#include <qpixmap.h>
+#include <qpushbutton.h>
+#include <qtooltip.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kfiledialog.h>
+
+#include "setupwizard.h"
+
+SetupWizard::SetupWizard( QWidget *parent, const char *name, bool modal, WFlags f ) : KWizard( parent, name, modal, f )
+{
+ welcomePage = new WelcomePage( this );
+ addPage( welcomePage, i18n( "Welcome to Krecipes" ) );
+
+ dbTypeSetupPage = new DBTypeSetupPage( this );
+ addPage( dbTypeSetupPage, i18n( "Database Type" ) );
+
+ sqliteSetupPage = new SQLiteSetupPage( this );
+ addPage( sqliteSetupPage, i18n( "Server Settings" ) );
+
+ permissionsSetupPage = new PermissionsSetupPage( this );
+ addPage( permissionsSetupPage, i18n( "Database Permissions" ) );
+
+ pSqlPermissionsSetupPage = new PSqlPermissionsSetupPage( this );
+ addPage( pSqlPermissionsSetupPage, i18n( "Database Permissions" ) );
+
+ serverSetupPage = new ServerSetupPage( this );
+ addPage( serverSetupPage, i18n( "Server Settings" ) );
+
+ dataInitializePage = new DataInitializePage( this );
+ addPage( dataInitializePage, i18n( "Initialize Database" ) );
+
+ savePage = new SavePage( this );
+ addPage( savePage, i18n( "Finish & Save Settings" ) );
+
+ setFinishEnabled( savePage, true ); // Enable finish button
+ setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
+
+ #if (!(HAVE_SQLITE || HAVE_SQLITE3))
+ #if (HAVE_MYSQL)
+ showPages( MySQL );
+ #else
+ #if (HAVE_POSTGRESQL)
+ showPages( PostgreSQL );
+ #endif
+ #endif
+ #else
+ showPages( SQLite );
+ #endif
+
+ connect( finishButton(), SIGNAL( clicked() ), this, SLOT( save() ) );
+ connect( dbTypeSetupPage, SIGNAL( showPages( DBType ) ), this, SLOT( showPages( DBType ) ) );
+
+}
+
+
+SetupWizard::~SetupWizard()
+{
+}
+
+
+void SetupWizard::showPages( DBType type )
+{
+ switch ( type ) {
+ case MySQL:
+ setAppropriate( serverSetupPage, true );
+ setAppropriate( permissionsSetupPage, true );
+ setAppropriate( pSqlPermissionsSetupPage, false );
+ setAppropriate( sqliteSetupPage, false );
+ break;
+ case PostgreSQL:
+ setAppropriate( serverSetupPage, true );
+ setAppropriate( pSqlPermissionsSetupPage, true );
+ setAppropriate( permissionsSetupPage, false );
+ setAppropriate( sqliteSetupPage, false );
+ break;
+ case SQLite:
+ setAppropriate( serverSetupPage, false );
+ setAppropriate( permissionsSetupPage, false );
+ setAppropriate( pSqlPermissionsSetupPage, false );
+ setAppropriate( sqliteSetupPage, true );
+ break;
+ }
+}
+
+
+WelcomePage::WelcomePage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+ QPixmap logoPixmap ( locate( "data", "krecipes/pics/wizard.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( logoPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( logo, 1, 1, Qt::AlignTop );
+
+ QSpacerItem *spacer_from_image = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_from_image, 1, 2 );
+
+ welcomeText = new QLabel( this );
+ welcomeText->setText( i18n( "<b><font size=\"+1\">Thank you very much for choosing Krecipes.</font></b><br>It looks like this is the first time you are using it. This wizard will help you with the initial setup so that you can start using it quickly.<br><br>Welcome, and enjoy cooking!" ) );
+ welcomeText->setMinimumWidth( 200 );
+ welcomeText->setMaximumWidth( 10000 );
+ welcomeText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ welcomeText->setAlignment( int( QLabel::WordBreak | QLabel::AlignTop ) );
+ layout->addWidget( welcomeText, 1, 3 );
+
+}
+
+PermissionsSetupPage::PermissionsSetupPage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+
+ // Logo
+ QPixmap permissionsSetupPixmap ( locate( "data", "krecipes/pics/dbpermissions.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( permissionsSetupPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addMultiCellWidget( logo, 1, 8, 1, 1, Qt::AlignTop );
+
+ // Spacer to separate the logo
+ QSpacerItem *logoSpacer = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( logoSpacer, 1, 2 );
+
+
+ // Explanation Text
+ permissionsText = new QLabel( this );
+ permissionsText->setText( i18n( "This dialog will allow you to specify a MySQL account that has the necessary permissions to access the Krecipes MySQL database.<br><br><b><font size=\"+1\">Most users that use Krecipes and MySQL for the first time can just leave the default parameters and press \'Next\'.</font></b> <br><br>If you set a MySQL root password before, or you have already permissions as normal user, click on the appropriate option. Otherwise the account 'root' will be used, with no password.<br><br>[For security reasons, we strongly encourage you to setup a MySQL root password if you have not done so yet. Just type as root: mysqladmin password <i>your_password</i>]" ) );
+
+ permissionsText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ permissionsText->setAlignment( int( QLabel::WordBreak | QLabel::AlignTop ) );
+ layout->addWidget( permissionsText, 1, 3 );
+
+ // Text spacer
+ QSpacerItem *textSpacer = new QSpacerItem( 10, 30, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( textSpacer, 2, 3 );
+
+
+ // "The user already has permissions" checkbox
+ noSetupCheckBox = new QCheckBox( i18n( "I have already set the necessary permissions" ), this, "noSetupCheckBox" );
+ layout->addWidget( noSetupCheckBox, 3, 3 );
+
+ QSpacerItem *checkBoxSpacer = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( checkBoxSpacer, 4, 3 );
+
+ // root checkbox
+ rootCheckBox = new QCheckBox( i18n( "I have already set a MySQL root/admin account" ), this, "rootCheckBox" );
+ layout->addWidget( rootCheckBox, 5, 3 );
+
+ QSpacerItem *rootInfoSpacer = new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( rootInfoSpacer, 6, 3 );
+
+ // MySQL root/admin info
+ QGroupBox *rootInfoGBox = new QGroupBox( this, "rootInfoGBox" );
+ rootInfoGBox->setTitle( i18n( "MySQL Administrator Account" ) );
+ rootInfoGBox->setEnabled( false ); // Disable by default
+ rootInfoGBox->setColumns( 2 );
+ rootInfoGBox->setInsideSpacing( 10 );
+ layout->addWidget( rootInfoGBox, 7, 3 );
+
+ // User Entry
+ QLabel *userLabel = new QLabel( rootInfoGBox );
+ userLabel->setText( i18n( "Username:" ) );
+ userEdit = new KLineEdit( rootInfoGBox );
+ userEdit->setText( "root" );
+
+ // Password Entry
+ QLabel *passLabel = new QLabel( rootInfoGBox );
+ passLabel->setText( i18n( "Password:" ) );
+ passEdit = new KLineEdit( rootInfoGBox );
+ passEdit->setEchoMode( QLineEdit::Password );
+
+ // Bottom spacer
+ QSpacerItem *bottomSpacer = new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( bottomSpacer, 8, 1 );
+
+ // Connect Signals & slots
+
+ connect( rootCheckBox, SIGNAL( toggled( bool ) ), rootInfoGBox, SLOT( setEnabled( bool ) ) );
+ connect( rootCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( rootCheckBoxChanged( bool ) ) );
+ connect( noSetupCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( noSetupCheckBoxChanged( bool ) ) );
+}
+
+void PermissionsSetupPage::rootCheckBoxChanged( bool on )
+{
+ if ( on )
+ noSetupCheckBox->setChecked( false ); // exclude mutually the options (both can be unset)
+}
+
+bool PermissionsSetupPage::doUserSetup()
+{
+ return ( !noSetupCheckBox->isChecked() );
+}
+
+bool PermissionsSetupPage::useAdmin()
+{
+ return ( rootCheckBox->isChecked() );
+}
+
+void PermissionsSetupPage::getAdmin( QString &adminName, QString &adminPass )
+{
+ adminName = userEdit->text();
+ adminPass = passEdit->text();
+}
+
+void PermissionsSetupPage::noSetupCheckBoxChanged( bool on )
+{
+ if ( on )
+ rootCheckBox->setChecked( false ); // exclude mutually the options (both can be unset)
+}
+
+
+PSqlPermissionsSetupPage::PSqlPermissionsSetupPage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+
+ // Logo
+ QPixmap permissionsSetupPixmap ( locate( "data", "krecipes/pics/dbpermissions.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( permissionsSetupPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addMultiCellWidget( logo, 1, 8, 1, 1, Qt::AlignTop );
+
+ // Spacer to separate the logo
+ QSpacerItem *logoSpacer = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( logoSpacer, 1, 2 );
+
+
+ // Explanation Text
+ permissionsText = new QLabel( this );
+ permissionsText->setText( i18n( "This dialog will allow you to specify a PostgreSQL account that has the necessary permissions to access the Krecipes PostgreSQL database. This account may either be a <b>PostgreSQL superuser</b> or have the ability to both <b>create new PostgreSQL users and databases</b>.<br><br>If no superuser or privileged account is given, the account 'postgres' will be attempted, with no password. If this is insufficient for your PostgreSQL setup, you <b>must</b> select the appropriate option below to enter the information of a privileged PostgreSQL account." ) );
+
+ permissionsText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ permissionsText->setAlignment( int( QLabel::WordBreak | QLabel::AlignTop ) );
+ layout->addWidget( permissionsText, 1, 3 );
+
+ // Text spacer
+ QSpacerItem *textSpacer = new QSpacerItem( 10, 30, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( textSpacer, 2, 3 );
+
+
+ // "The user already has permissions" checkbox
+ noSetupCheckBox = new QCheckBox( i18n( "I have already set the necessary permissions" ), this, "noSetupCheckBox" );
+ layout->addWidget( noSetupCheckBox, 3, 3 );
+
+ QSpacerItem *checkBoxSpacer = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( checkBoxSpacer, 4, 3 );
+
+ // root checkbox
+ rootCheckBox = new QCheckBox( i18n( "I have already set a superuser or privileged account" ), this, "rootCheckBox" );
+ layout->addWidget( rootCheckBox, 5, 3 );
+
+ QSpacerItem *rootInfoSpacer = new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( rootInfoSpacer, 6, 3 );
+
+ // MySQL root/admin info
+ QGroupBox *rootInfoGBox = new QGroupBox( this, "rootInfoGBox" );
+ rootInfoGBox->setTitle( i18n( "PostgreSQL Superuser or Privileged Account" ) );
+ rootInfoGBox->setEnabled( false ); // Disable by default
+ rootInfoGBox->setColumns( 2 );
+ rootInfoGBox->setInsideSpacing( 10 );
+ layout->addWidget( rootInfoGBox, 7, 3 );
+
+ // User Entry
+ QLabel *userLabel = new QLabel( rootInfoGBox );
+ userLabel->setText( i18n( "Username:" ) );
+ userEdit = new KLineEdit( rootInfoGBox );
+ userEdit->setText( "postgres" );
+
+ // Password Entry
+ QLabel *passLabel = new QLabel( rootInfoGBox );
+ passLabel->setText( i18n( "Password:" ) );
+ passEdit = new KLineEdit( rootInfoGBox );
+ passEdit->setEchoMode( QLineEdit::Password );
+
+ // Bottom spacer
+ QSpacerItem *bottomSpacer = new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( bottomSpacer, 8, 1 );
+
+ // Connect Signals & slots
+
+ connect( rootCheckBox, SIGNAL( toggled( bool ) ), rootInfoGBox, SLOT( setEnabled( bool ) ) );
+ connect( rootCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( rootCheckBoxChanged( bool ) ) );
+ connect( noSetupCheckBox, SIGNAL( toggled( bool ) ), this, SLOT( noSetupCheckBoxChanged( bool ) ) );
+}
+
+void PSqlPermissionsSetupPage::rootCheckBoxChanged( bool on )
+{
+ if ( on )
+ noSetupCheckBox->setChecked( false ); // exclude mutually the options (both can be unset)
+}
+
+bool PSqlPermissionsSetupPage::doUserSetup()
+{
+ return ( !noSetupCheckBox->isChecked() );
+}
+
+bool PSqlPermissionsSetupPage::useAdmin()
+{
+ return ( rootCheckBox->isChecked() );
+}
+
+void PSqlPermissionsSetupPage::getAdmin( QString &adminName, QString &adminPass )
+{
+ adminName = userEdit->text();
+ adminPass = passEdit->text();
+}
+
+void PSqlPermissionsSetupPage::noSetupCheckBoxChanged( bool on )
+{
+ if ( on )
+ rootCheckBox->setChecked( false ); // exclude mutually the options (both can be unset)
+}
+
+
+ServerSetupPage::ServerSetupPage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+
+ // Image
+
+ QPixmap serverSetupPixmap ( locate( "data", "krecipes/pics/network.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( serverSetupPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addMultiCellWidget( logo, 1, 8, 1, 1, Qt::AlignTop );
+
+ QSpacerItem *spacer_from_image = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_from_image, 1, 2 );
+
+
+ // Explanation text
+ serverSetupText = new QLabel( this );
+ serverSetupText->setText( i18n( "In this dialog you can adjust the database server settings.<br><br><b>Warning: Passwords are stored in plain text and could potentially be compromised. We recommend that you create a username and password combination solely for use by Krecipes.</b>" ) );
+ serverSetupText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ serverSetupText->setAlignment( int( QLabel::AlignTop | QLabel::WordBreak ) );
+ layout->addWidget( serverSetupText, 1, 3 );
+
+ // Text spacer
+
+ QSpacerItem* textSpacer = new QSpacerItem( 10, 30, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( textSpacer, 2, 3 );
+
+ // Input Boxes
+
+ QGroupBox *inputGBox = new QGroupBox( this, "inputGBox" );
+ inputGBox->setFrameStyle( QFrame::NoFrame );
+ inputGBox->setInsideSpacing( 10 );
+ inputGBox->setColumns( 2 );
+ layout->addWidget( inputGBox, 3, 3 );
+
+ // Username Input
+
+ QLabel* usernameText = new QLabel( i18n( "Username:" ), inputGBox );
+ usernameText->setFixedSize( QSize( 100, 20 ) );
+ usernameText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+
+ usernameEdit = new KLineEdit( inputGBox );
+ usernameEdit->setFixedSize( QSize( 120, 20 ) );
+ usernameEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ // get username
+ uid_t userID;
+ QString username;
+ struct passwd *user;
+ userID = getuid();
+ user = getpwuid ( userID );
+ username = user->pw_name;
+
+ usernameEdit->setText( username );
+
+
+ // Password
+
+ QLabel* passwordText = new QLabel( i18n( "Password:" ), inputGBox );
+ passwordText->setFixedSize( QSize( 100, 20 ) );
+ passwordText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+
+ passwordEdit = new KLineEdit( inputGBox );
+ passwordEdit->setEchoMode( QLineEdit::Password );
+ passwordEdit->setFixedSize( QSize( 120, 20 ) );
+ passwordEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+
+ // DB Name
+
+ QLabel* dbNameText = new QLabel( i18n( "Database name:" ), inputGBox );
+ dbNameText->setFixedSize( QSize( 100, 20 ) );
+ dbNameText->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+
+ dbNameEdit = new KLineEdit( inputGBox );
+ dbNameEdit->setFixedSize( QSize( 120, 20 ) );
+ dbNameEdit->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ dbNameEdit->setText( "Krecipes" );
+
+
+ // Spacer from box
+ QSpacerItem* spacerFromBox = new QSpacerItem( 10, 20, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerFromBox, 4, 3 );
+
+
+ // Remote server checkbox
+
+ remoteServerCheckBox = new QCheckBox( i18n( "The server is remote" ), this, "remoteServerCheckBox" );
+ layout->addWidget( remoteServerCheckBox, 5, 3 );
+
+ // Spacer from CheckBox
+ QSpacerItem* spacerFromCheckBox = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacerFromCheckBox, 6, 3 );
+
+ // Server & Client Box
+ QGroupBox *serverSettingsGBox = new QGroupBox( this, "serverSettingsGBox" );
+ serverSettingsGBox->setTitle( i18n( "Server / Client Settings" ) );
+ serverSettingsGBox->setEnabled( false ); // Disable by default
+ serverSettingsGBox->setInsideSpacing( 10 );
+ serverSettingsGBox->setColumns( 2 );
+ layout->addWidget( serverSettingsGBox, 7, 3 );
+
+
+ // Server
+ ( void ) new QLabel( i18n( "Server:" ), serverSettingsGBox );
+ serverEdit = new KLineEdit( serverSettingsGBox );
+ serverEdit->setText( "localhost" );
+
+ // Client
+ ( void ) new QLabel( i18n( "Client:" ), serverSettingsGBox );
+ clientEdit = new KLineEdit( serverSettingsGBox );
+ clientEdit->setText( "localhost" );
+
+ // Bottom Spacers
+
+ QSpacerItem* bottomSpacer = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( bottomSpacer, 8, 1 );
+
+ //QSpacerItem* spacerRight = new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
+ //layout->addItem( spacerRight, 7, 4 );
+
+ // Signals & Slots
+ connect( remoteServerCheckBox, SIGNAL( toggled( bool ) ), serverSettingsGBox, SLOT( setEnabled( bool ) ) );
+
+}
+
+QString ServerSetupPage::server( void )
+{
+ return ( serverEdit->text() );
+}
+
+QString ServerSetupPage::user( void )
+{
+ return ( usernameEdit->text() );
+}
+
+QString ServerSetupPage::password( void )
+{
+ return ( passwordEdit->text() );
+}
+
+QString ServerSetupPage::dbName( void )
+{
+ return ( dbNameEdit->text() );
+}
+
+void ServerSetupPage::getServerInfo( bool &isRemote, QString &host, QString &client, QString &dbName, QString &user, QString &pass, int &port )
+{
+ isRemote = remoteServerCheckBox->isChecked();
+ host = serverEdit->text();
+ client = clientEdit->text();
+ user = usernameEdit->text();
+ pass = passwordEdit->text();
+ dbName = dbNameEdit->text();
+ port = 0;
+}
+
+SQLiteSetupPage::SQLiteSetupPage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+
+ // Image
+
+ QPixmap serverSetupPixmap ( locate( "data", "krecipes/pics/network.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( serverSetupPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addMultiCellWidget( logo, 1, 4, 1, 1, Qt::AlignTop );
+
+ QSpacerItem *spacer_from_image = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_from_image, 1, 2 );
+
+
+ // Explanation text
+ serverSetupText = new QLabel( this );
+ serverSetupText->setText( i18n( "In this dialog you can adjust SQLite settings." ) );
+ serverSetupText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ serverSetupText->setAlignment( int( QLabel::AlignTop | QLabel::AlignJustify ) );
+ layout->addWidget( serverSetupText, 1, 3 );
+
+ // Text spacer
+
+ QSpacerItem* textSpacer = new QSpacerItem( 10, 30, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( textSpacer, 2, 3 );
+
+ // Input Boxes
+
+ QHBox *hbox = new QHBox( this );
+
+ ( void ) new QLabel( i18n( "Database file:" ), hbox );
+
+ fileEdit = new KLineEdit( hbox );
+ fileEdit->setText( locateLocal ( "appdata", "krecipes.krecdb" ) );
+ hbox->setStretchFactor( fileEdit, 2 );
+
+ KIconLoader il;
+ QPushButton *file_select = new QPushButton( il.loadIcon( "fileopen", KIcon::NoGroup, 16 ), QString::null, hbox );
+ QToolTip::add
+ ( file_select, i18n( "Open file dialog" ) );
+ file_select->setFixedWidth( 25 );
+
+ layout->addWidget( hbox, 3, 3 );
+
+ // Bottom Spacers
+
+ QSpacerItem* bottomSpacer = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding );
+ layout->addItem( bottomSpacer, 4, 1 );
+
+ connect( file_select, SIGNAL( clicked() ), SLOT( selectFile() ) );
+}
+
+QString SQLiteSetupPage::dbFile( void ) const
+{
+ return ( fileEdit->text() );
+}
+
+void SQLiteSetupPage::selectFile()
+{
+ KFileDialog dialog( QString::null, "*.*|All Files", this, "dialog", true );
+ if ( dialog.exec() == QDialog::Accepted ) {
+ fileEdit->setText( dialog.selectedFile() );
+ }
+}
+
+
+SavePage::SavePage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+ QPixmap logoPixmap ( locate( "data", "krecipes/pics/save.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( logoPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addWidget( logo, 1, 1, Qt::AlignTop );
+
+ QSpacerItem *spacer_from_image = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_from_image, 1, 2 );
+
+ saveText = new QLabel( this );
+ saveText->setText( i18n( "Congratulations; all the necessary configuration setup is done. Press 'Finish' to continue, and enjoy cooking!" ) );
+ saveText->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
+
+ saveText->setAlignment( int( QLabel::WordBreak | QLabel::AlignVCenter ) );
+ layout->addWidget( saveText, 1, 3 );
+
+
+}
+
+void SetupWizard::save( void )
+{
+ kdDebug() << "Setting parameters in kconfig..." << endl;
+ KConfig *config = kapp->config();
+
+ // Save the database type
+ QString sDBType;
+
+ switch ( dbTypeSetupPage->dbType() ) {
+ case MySQL:
+ sDBType = "MySQL";
+ break;
+ case PostgreSQL:
+ sDBType = "PostgreSQL";
+ break;
+ default:
+ sDBType = "SQLite";
+ break;
+ }
+
+ config->setGroup( "DBType" );
+ config->writeEntry( "Type", sDBType );
+ kdDebug() << "DB type set in kconfig was... " << sDBType << endl;
+ // Save the server data if needed
+ if ( !( dbTypeSetupPage->dbType() == SQLite ) ) {
+ config->setGroup( "Server" );
+ config->writeEntry( "Host", serverSetupPage->server() );
+ config->writeEntry( "Username", serverSetupPage->user() );
+ config->writeEntry( "Password", serverSetupPage->password() );
+ config->writeEntry( "DBName", serverSetupPage->dbName() );
+ kdDebug() << "Finished setting the database parameters for MySQL or PostgreSQL (non SQLite)..." << endl;
+ }
+ else {
+ config->setGroup( "Server" );
+ config->writeEntry( "DBFile", sqliteSetupPage->dbFile() );
+ }
+
+ // Indicate that settings were already made
+
+ config->setGroup( "Wizard" );
+ config->writeEntry( "SystemSetup", true );
+ config->writeEntry( "Version", "0.9" );
+ kdDebug() << "Setting in kconfig the lines to disable wizard startup..." << sDBType << endl;
+}
+
+void SetupWizard::getOptions( bool &setupUser, bool &initializeData, bool &doUSDAImport )
+{
+ setupUser = permissionsSetupPage->doUserSetup() && pSqlPermissionsSetupPage->doUserSetup();
+ initializeData = dataInitializePage->doInitialization();
+ doUSDAImport = dataInitializePage->doUSDAImport();
+}
+
+void SetupWizard::getAdminInfo( bool &enabled, QString &adminUser, QString &adminPass, const QString &dbType )
+{
+ enabled = permissionsSetupPage->useAdmin() || pSqlPermissionsSetupPage->useAdmin();
+ if ( dbType == "MySQL" )
+ permissionsSetupPage->getAdmin( adminUser, adminPass );
+ else
+ pSqlPermissionsSetupPage->getAdmin( adminUser, adminPass );
+}
+
+void SetupWizard::getServerInfo( bool &isRemote, QString &host, QString &client, QString &dbName, QString &user, QString &pass, int &port )
+{
+ serverSetupPage->getServerInfo( isRemote, host, client, dbName, user, pass, port );
+ if ( dbTypeSetupPage->dbType() == SQLite )
+ dbName = sqliteSetupPage->dbFile();
+}
+
+DataInitializePage::DataInitializePage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+ // Explanation Text// Widgets
+ QLabel *logo;
+ //QLabel *serverSetupText;
+ //KLineEdit *usernameEdit;
+ //KLineEdit *passwordEdit;
+ //KLineEdit *dbNameEdit;
+ initializeText = new QLabel( this );
+ initializeText->setText( i18n( "Krecipes comes with some delicious default recipes and useful data. <br><br>Would you like to initialize your database with those? Note that this will erase all your previous recipes if you have any. " ) );
+
+ initializeText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ initializeText->setAlignment( int( QLabel::WordBreak | QLabel::AlignTop ) );
+ layout->addWidget( initializeText, 1, 3 );
+
+ // Logo
+ QPixmap dataInitializePixmap ( locate( "data", "krecipes/pics/recipes.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( dataInitializePixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addMultiCellWidget( logo, 1, 8, 1, 1, Qt::AlignTop );
+
+ // Spacer to separate the logo
+ QSpacerItem *logoSpacer = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( logoSpacer, 1, 2 );
+
+ // Initialize data checkbox
+
+ initializeCheckBox = new QCheckBox( i18n( "Yes please, initialize the database with the examples" ), this, "initializeCheckBox" );
+ layout->addWidget( initializeCheckBox, 3, 3 );
+
+ QSpacerItem *textInfoSpacer = new QSpacerItem( 0, 50, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( textInfoSpacer, 4, 3 );
+
+ USDAImportText = new QLabel( i18n( "Krecipes can import nutrient data from the USDA's nutrient database for over 400 foods. A total of 43 food properties are included for each food, such as energy, fat, vitamin C, etc.<br><br>Would you like to import this data now? Note that this operation is safe to use on an existing database, and no data loss will occur. This operation may take several minutes." ), this );
+ layout->addWidget( USDAImportText, 5, 3 );
+
+ QSpacerItem *importInfoSpacer = new QSpacerItem( 0, 50, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( importInfoSpacer, 6, 3 );
+
+ USDAImportCheckBox = new QCheckBox( i18n( "Yes please, load the database with nutrient data for 400+ foods. (Note: English only.)" ), this, "USDAImportCheckBox" );
+ layout->addWidget( USDAImportCheckBox, 7, 3 );
+}
+
+bool DataInitializePage::doInitialization( void )
+{
+ return ( initializeCheckBox->isChecked() );
+}
+
+bool DataInitializePage::doUSDAImport( void )
+{
+ return ( USDAImportCheckBox->isChecked() );
+}
+
+DBTypeSetupPage::DBTypeSetupPage( QWidget *parent ) : QWidget( parent )
+{
+ QGridLayout * layout = new QGridLayout( this, 1, 1, 0, 0 );
+ QSpacerItem *spacer_top = new QSpacerItem( 10, 10, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( spacer_top, 0, 1 );
+ QSpacerItem *spacer_left = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_left, 1, 0 );
+
+
+ // Image
+
+ QPixmap serverSetupPixmap ( locate( "data", "krecipes/pics/network.png" ) );
+ logo = new QLabel( this );
+ logo->setPixmap( serverSetupPixmap );
+ logo->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
+ layout->addMultiCellWidget( logo, 1, 4, 1, 1, Qt::AlignTop );
+
+ QSpacerItem *spacer_from_image = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::Minimum );
+ layout->addItem( spacer_from_image, 1, 2 );
+
+
+ // Explanation text
+ dbTypeSetupText = new QLabel( this );
+ dbTypeSetupText->setText( i18n( "Choose the type of database that you want to use. Most users will want to choose a simple local database here. However, you can also use remote servers by means of a MySQL or PostgreSQL database." ) );
+ dbTypeSetupText->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
+ dbTypeSetupText->setAlignment( int( QLabel::AlignTop | QLabel::WordBreak ) );
+ layout->addWidget( dbTypeSetupText, 1, 3 );
+
+ // Text spacer
+
+ QSpacerItem* textSpacer = new QSpacerItem( 10, 30, QSizePolicy::Minimum, QSizePolicy::Fixed );
+ layout->addItem( textSpacer, 2, 3 );
+
+
+
+ // Database type choice
+ bg = new QVButtonGroup( this );
+ layout->addWidget( bg, 3, 3 );
+
+ liteCheckBox = new QRadioButton( i18n( "Simple Local File (SQLite)" ), bg, "liteCheckBox" );
+ mysqlCheckBox = new QRadioButton( i18n( "Local or Remote MySQL Database" ), bg, "liteCheckBox" );
+ psqlCheckBox = new QRadioButton( i18n( "Local or Remote PostgreSQL Database" ), bg, "psqlCheckBox" );
+ bg->setButton( 0 ); // By default, SQLite
+
+#if (!HAVE_MYSQL)
+ mysqlCheckBox->setEnabled( false );
+#endif
+
+#if (!HAVE_POSTGRESQL)
+ psqlCheckBox->setEnabled( false );
+#endif
+
+#if (!(HAVE_SQLITE || HAVE_SQLITE3))
+ liteCheckBox->setEnabled( false );
+#if (HAVE_MYSQL)
+
+ bg->setButton( 1 ); // Otherwise by default liteCheckBox is checked even if it's disabled
+#else
+ #if (HAVE_POSTGRESQL)
+
+ bg->setButton( 2 );
+#endif
+ #endif
+#endif
+
+
+ QSpacerItem *spacer_bottom = new QSpacerItem( 10, 10, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
+ layout->addItem( spacer_bottom, 4, 3 );
+
+ connect( bg, SIGNAL( clicked( int ) ), this, SLOT( setPages( int ) ) );
+
+}
+
+int DBTypeSetupPage::dbType( void )
+{
+ //int id=bg->selectedId(); //QT 3.2
+ int id = bg->id( bg->selected() ); //QT 3.1
+
+ switch ( id ) {
+ case 1:
+ return ( MySQL ); // MySQL (note index=0,1....)
+ case 2:
+ return ( PostgreSQL );
+ default:
+ return ( SQLite );
+ }
+}
+
+/*
+** hides/shows pages given the radio button state
+*/
+
+void DBTypeSetupPage::setPages( int rb )
+{
+ switch ( rb ) {
+ case 1:
+ emit showPages( MySQL );
+ break;
+ case 2:
+ emit showPages( PostgreSQL );
+ break;
+ default:
+ emit showPages( SQLite );
+ break;
+ }
+}
+
+#include "setupwizard.moc"
diff --git a/krecipes/src/setupwizard.h b/krecipes/src/setupwizard.h
new file mode 100644
index 0000000..5a24fa2
--- /dev/null
+++ b/krecipes/src/setupwizard.h
@@ -0,0 +1,224 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef SETUPWIZARD_H
+#define SETUPWIZARD_H
+
+#include <qvbuttongroup.h>
+#include <qcheckbox.h>
+#include <qradiobutton.h>
+#include <qlabel.h>
+#include <kwizard.h>
+#include <klineedit.h>
+#include <ktextedit.h>
+
+
+/**
+@author Unai Garro
+*/
+
+class WelcomePage;
+class DBTypeSetupPage;
+class PermissionsSetupPage;
+class PSqlPermissionsSetupPage;
+class ServerSetupPage;
+class DataInitializePage;
+class SavePage;
+class SQLiteSetupPage;
+
+enum DBType {SQLite, MySQL, PostgreSQL};
+
+class SetupWizard: public KWizard
+{
+ Q_OBJECT
+public:
+
+ SetupWizard( QWidget *parent = 0, const char *name = 0, bool modal = true, WFlags f = 0 );
+ ~SetupWizard();
+ void getOptions( bool &setupUser, bool &initializeData, bool &doUSDAImport );
+ void getAdminInfo( bool &enabled, QString &adminUser, QString &adminPass, const QString &dbType );
+ void getServerInfo( bool &isRemote, QString &host, QString &client, QString &dbName, QString &user, QString &pass, int &port );
+private:
+ // Widgets
+ WelcomePage *welcomePage;
+ DBTypeSetupPage *dbTypeSetupPage;
+ PermissionsSetupPage *permissionsSetupPage;
+ PSqlPermissionsSetupPage *pSqlPermissionsSetupPage;
+ ServerSetupPage *serverSetupPage;
+ DataInitializePage *dataInitializePage;
+ SQLiteSetupPage *sqliteSetupPage;
+ SavePage *savePage;
+
+private slots:
+ void save( void );
+ void showPages( DBType );
+
+};
+
+class WelcomePage: public QWidget
+{
+public:
+ // Methods
+ WelcomePage( QWidget *parent );
+
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *welcomeText;
+
+};
+
+
+class PermissionsSetupPage: public QWidget
+{
+ Q_OBJECT
+public:
+ // Methods
+ PermissionsSetupPage( QWidget *parent );
+ bool doUserSetup( void );
+ bool useAdmin( void );
+ void getAdmin( QString &adminName, QString &adminPass );
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *permissionsText;
+ QCheckBox *noSetupCheckBox;
+ QCheckBox *rootCheckBox;
+ QLineEdit *userEdit;
+ QLineEdit *passEdit;
+
+private slots:
+ void rootCheckBoxChanged( bool on );
+ void noSetupCheckBoxChanged( bool on );
+
+};
+
+class PSqlPermissionsSetupPage: public QWidget
+{
+ Q_OBJECT
+public:
+ // Methods
+ PSqlPermissionsSetupPage( QWidget *parent );
+ bool doUserSetup( void );
+ bool useAdmin( void );
+ void getAdmin( QString &adminName, QString &adminPass );
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *permissionsText;
+ QCheckBox *noSetupCheckBox;
+ QCheckBox *rootCheckBox;
+ QLineEdit *userEdit;
+ QLineEdit *passEdit;
+
+private slots:
+ void rootCheckBoxChanged( bool on );
+ void noSetupCheckBoxChanged( bool on );
+
+};
+
+class ServerSetupPage: public QWidget
+{
+public:
+ // Methods
+ ServerSetupPage( QWidget *parent );
+ QString user( void );
+ QString password( void );
+ QString dbName( void );
+ QString server( void );
+ QString client( void );
+ void getServerInfo( bool &isRemote, QString &host, QString &client, QString &dbName, QString &user, QString &pass, int &port );
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *serverSetupText;
+ KLineEdit *usernameEdit;
+ KLineEdit *passwordEdit;
+ KLineEdit *dbNameEdit;
+ QCheckBox *remoteServerCheckBox;
+ KLineEdit *serverEdit;
+ KLineEdit *clientEdit;
+};
+
+
+class SQLiteSetupPage: public QWidget
+{
+ Q_OBJECT
+
+public:
+ // Methods
+ SQLiteSetupPage( QWidget *parent );
+ QString dbFile() const;
+
+private slots:
+ void selectFile();
+
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *serverSetupText;
+ KLineEdit *fileEdit;
+};
+
+
+class DataInitializePage: public QWidget
+{
+public:
+ // Methods
+ DataInitializePage( QWidget *parent );
+ bool doInitialization( void );
+ bool doUSDAImport( void );
+
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *initializeText;
+ QLabel *USDAImportText;
+ QCheckBox *initializeCheckBox;
+ QCheckBox *USDAImportCheckBox;
+
+};
+
+class SavePage: public QWidget
+{
+public:
+ // Methods
+ SavePage( QWidget *parent );
+private:
+ // Widgets
+ QLabel *logo;
+ QLabel *saveText;
+
+};
+
+class DBTypeSetupPage: public QWidget
+{
+
+ Q_OBJECT
+
+public:
+ // Methods
+ DBTypeSetupPage( QWidget *parent );
+ int dbType( void );
+private:
+ // Widgets
+ QLabel *dbTypeSetupText;
+ QLabel *logo;
+ QVButtonGroup *bg;
+ QRadioButton *liteCheckBox;
+ QRadioButton *mysqlCheckBox;
+ QRadioButton *psqlCheckBox;
+private slots:
+ void setPages( int rb ); // hides/shows pages given the radio button state
+signals:
+ void showPages( DBType );
+};
+
+#endif
diff --git a/krecipes/src/shoppingcalculator.cpp b/krecipes/src/shoppingcalculator.cpp
new file mode 100644
index 0000000..e95624a
--- /dev/null
+++ b/krecipes/src/shoppingcalculator.cpp
@@ -0,0 +1,78 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "shoppingcalculator.h"
+
+#include <kdebug.h>
+
+#include "propertycalculator.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/ingredientlist.h"
+
+#include "backends/recipedb.h"
+
+//NOTE: The code as-is uses the lower value if an ingredient range exists
+// However, the shopping list is calculated using the upper value
+// because the values are already adjusted prior to being passed
+// to these functions
+
+void calculateShopping( const ElementList &recipeList, IngredientList *ingredientList, RecipeDB *db )
+{
+ for ( ElementList::const_iterator recipe_it = recipeList.begin(); recipe_it != recipeList.end(); ++recipe_it ) {
+ Recipe rec;
+ db->loadRecipe( &rec, RecipeDB::Ingredients, ( *recipe_it ).id );
+ sum( ingredientList, &( rec.ingList ), db );
+ }
+}
+
+void sum( IngredientList *totalIngredientList, IngredientList *newIngredientList, RecipeDB *db )
+{
+ for ( IngredientList::const_iterator ing_it = newIngredientList->begin(); ing_it != newIngredientList->end(); ++ing_it ) {
+ IngredientList::iterator pos_it;
+
+ // Find out if ingredient exists in list already
+ int pos = totalIngredientList->find( ( *ing_it ).ingredientID );
+
+ if ( pos >= 0 ) // the ingredient is already listed
+ {
+ pos_it = totalIngredientList->at( pos );
+
+ // Variables to store the new total
+ Ingredient result;
+ bool converted;
+
+ // Do the conversion
+ // try to with this and next in the list until conversion rate is
+ // found or end of list is reached
+ IngredientList::iterator lastpos_it; // for 'backup'
+ do
+ {
+ lastpos_it = pos_it;
+
+ // Try to convert
+ converted = autoConvert( db, *ing_it, *pos_it, result );
+ }
+ while ( ( !converted ) && ( ( ( pos_it = totalIngredientList->find( ++pos_it, ( *ing_it ).ingredientID ) ) ) != totalIngredientList->end() ) );
+
+ // If the conversion was succesful, Set the New Values
+ if ( converted ) {
+ *lastpos_it = result;
+ }
+ else // Otherwise append this ingredient at the end of the list
+ {
+ // Insert ingredient ID in the list
+ totalIngredientList->append( *ing_it );
+ }
+ }
+ else // The ingredient is not in the list, just append
+ {
+ totalIngredientList->append( *ing_it );
+ }
+ }
+}
diff --git a/krecipes/src/shoppingcalculator.h b/krecipes/src/shoppingcalculator.h
new file mode 100644
index 0000000..1d00323
--- /dev/null
+++ b/krecipes/src/shoppingcalculator.h
@@ -0,0 +1,23 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro *
+* ugarro@users.sourceforge.net *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef SHOPPINGCALCULATOR_H
+#define SHOPPINGCALCULATOR_H
+
+class ElementList;
+class IngredientList;
+class RecipeDB;
+
+/**
+@author Unai Garro
+*/
+void calculateShopping( const ElementList &recipeList, IngredientList *ingredientList, RecipeDB *db );
+void sum ( IngredientList *totalIngredientList, IngredientList *newIngredientList, RecipeDB *db );
+int autoConvertUnits( RecipeDB* database, double amount1, int unit1, double amount2, int unit2, double &newAmount, int &newID );
+#endif
diff --git a/krecipes/src/tests/Makefile.am b/krecipes/src/tests/Makefile.am
new file mode 100644
index 0000000..f63ead5
--- /dev/null
+++ b/krecipes/src/tests/Makefile.am
@@ -0,0 +1,19 @@
+INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../importers -I$(srcdir)/../exporters $(all_includes)
+
+AM_LDFLAGS = $(KDE_RPATH) $(all_libraries)
+
+check_PROGRAMS = kretest mmftest mx2test mxptest rezkonvtest nyctest recipemltest
+
+noinst_HEADERS = importertest.h exportertest.h
+
+METASOURCES = AUTO
+
+LDADD = ../importers/libkrecipesimporters.la ../exporters/libkrecipesexporters.la ../backends/libkrecipesdbs.la ../datablocks/libdatablocks.la $(LIB_QT) $(LIB_KHTML) $(LIB_KSPELL)
+
+kretest_SOURCES = kretest.cpp
+mmftest_SOURCES = mmftest.cpp
+mx2test_SOURCES = mx2test.cpp
+mxptest_SOURCES = mxptest.cpp
+rezkonvtest_SOURCES = rezkonvtest.cpp
+nyctest_SOURCES = nyctest.cpp
+recipemltest_SOURCES = recipemltest.cpp
diff --git a/krecipes/src/tests/checks.h b/krecipes/src/tests/checks.h
new file mode 100644
index 0000000..f115f98
--- /dev/null
+++ b/krecipes/src/tests/checks.h
@@ -0,0 +1,181 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CHECKS_H
+#define CHECKS_H
+
+#include <cmath>
+#include <iostream>
+
+#include <qstring.h>
+#include <qpixmap.h>
+#include <qimage.h>
+
+#include "datablocks/categorytree.h"
+#include "datablocks/rating.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+void check( const RatingList &rating, const RatingList &base );
+
+bool check(const QString &txt, const QString &a, const QString &b)
+{
+ if ( a != b ) {
+ cout << "ERROR: Tested " << txt.latin1() << ", expected" << endl;
+ cout << "'" << b.latin1() << "' (" << b.length() << " chars)" << endl;
+ cout << "but got" << endl;
+ cout << "'" << a.latin1() << "' (" << a.length() << " chars)" << endl;
+ exit( 1 );
+ }
+
+ return true;
+}
+
+bool check(const QString &txt, int a, int b)
+{
+ if ( a != b ) {
+ cout << "ERROR: Tested " << txt.latin1() << ", expected" << endl;
+ cout << "'" << b << "'" << endl;
+ cout << "but got" << endl;
+ cout << "'" << a << "'" << endl;
+ exit( 1 );
+ }
+
+ return true;
+}
+
+bool check(const QString &txt, double a, double b)
+{
+ if ( fabs(a - b) > 1e-10 ) {
+ cout << "ERROR: Tested " << txt.latin1() << ", expected" << endl;
+ cout << "'" << b << "'" << endl;
+ cout << "but got" << endl;
+ cout << "'" << a << "'" << endl;
+ exit( 1 );
+ }
+
+ return true;
+}
+
+bool check(const QString &txt, const QPixmap &a, const QPixmap &b)
+{
+ if ( a.size() != b.size() ) {
+
+ cout << "ERROR: Tested " << txt.latin1() << ": photos differ" << endl;
+ // exit( 1 );
+ }
+
+ return true;
+}
+
+void check( const IngredientData &ing, const IngredientData &base_ing, int ing_num )
+{
+ check( QString::number(ing_num)+": Ingredient name", ing.name, base_ing.name );
+ check( QString::number(ing_num)+": Ingredient amount", ing.amount,base_ing.amount );
+ check( QString::number(ing_num)+": Ingredient amount_offset", ing.amount_offset,base_ing.amount_offset );
+ check( QString::number(ing_num)+": Ingredient singular unit", ing.units.name, base_ing.units.name );
+ check( QString::number(ing_num)+": Ingredient plural unit", ing.units.plural, base_ing.units.plural );
+ check( QString::number(ing_num)+": Ingredient group", ing.group, base_ing.group );
+
+ ElementList::const_iterator prep_it = ing.prepMethodList.begin();
+ ElementList::const_iterator base_prep_it = base_ing.prepMethodList.begin();
+ for ( ; prep_it != ing.prepMethodList.end(); ++prep_it, ++base_prep_it ) {
+ check( QString::number(ing_num)+": Ingredient prep_method", (*prep_it).name, (*base_prep_it).name );
+ }
+}
+
+void check( const Recipe &recipe, const Recipe &base )
+{
+ check( "Recipe title", recipe.title, base.title );
+ check( "Yield base", recipe.yield.amount, base.yield.amount );
+ check( "Yield offset", recipe.yield.amount_offset, base.yield.amount_offset );
+ check( "Yield type", recipe.yield.type, base.yield.type );
+ check( "Instructions", recipe.instructions, base.instructions );
+ check( "Photo", recipe.photo, base.photo );
+
+ check( recipe.ratingList, base.ratingList );
+
+ int cat_num = 1;
+ ElementList::const_iterator cat_it = recipe.categoryList.begin();
+ ElementList::const_iterator base_cat_it = base.categoryList.begin();
+ for ( ; cat_it != recipe.categoryList.end() || base_cat_it != base.categoryList.end(); ++cat_it, ++base_cat_it ) {
+ check( QString::number(cat_num)+": Category", (*cat_it).name, (*base_cat_it).name );
+ ++cat_num;
+ }
+ check( "category count", cat_num-1, base.categoryList.count() );
+
+ int author_num = 1;
+ ElementList::const_iterator author_it = recipe.authorList.begin();
+ ElementList::const_iterator base_author_it = base.authorList.begin();
+ for ( ; author_it != recipe.authorList.end() || base_author_it != base.authorList.end(); ++author_it, ++base_author_it ) {
+ check( QString::number(author_num)+": Author", (*author_it).name, (*base_author_it).name );
+ ++author_num;
+ }
+ check( "author count", author_num-1, base.authorList.count() );
+
+ int ing_num = 1;
+ IngredientList::const_iterator ing_it = recipe.ingList.begin();
+ IngredientList::const_iterator base_ing_it = base.ingList.begin();
+ for ( ; ing_it != recipe.ingList.end() || base_ing_it != base.ingList.end(); ++ing_it, ++base_ing_it ) {
+ check( *ing_it, *base_ing_it, ing_num );
+
+ QValueList<IngredientData>::const_iterator base_sub_it = (*base_ing_it).substitutes.begin();
+ for ( QValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ++sub_it, ++base_sub_it ) {
+ check( *sub_it, *base_sub_it, ing_num+1000 );
+ }
+
+ ++ing_num;
+ }
+ check( "ingredient count", ing_num-1, base.ingList.count() );
+}
+
+bool check( const CategoryTree *catStructure, const CategoryTree *baseCatStructure )
+{
+ CategoryTree * it = catStructure->firstChild();
+ CategoryTree * base_it = baseCatStructure->firstChild();
+ for ( ; it && base_it; it = it->nextSibling(), base_it = base_it->nextSibling() ) {
+ check( it, base_it );
+
+ if ( it->category.name != base_it->category.name ) {
+ printf("FAILED: Category structure differs\n");
+ exit(1);
+ }
+ }
+
+ if ( base_it != it ) { //these should both be NULL
+ printf("FAILED: Category structure differs\n");
+ exit(1);
+ }
+
+ return true;
+}
+
+void check( const RatingList &rating, const RatingList &base )
+{
+ RatingList::const_iterator rating_it = rating.begin();
+ RatingList::const_iterator base_rating_it = base.begin();
+ for ( ; rating_it != rating.end() || base_rating_it != base.end(); ++rating_it, ++base_rating_it ) {
+ check("checking rater",(*rating_it).rater,(*base_rating_it).rater);
+ check("checking comment",(*rating_it).comment,(*base_rating_it).comment);
+
+ RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin();
+ RatingCriteriaList::const_iterator base_rc_it = (*base_rating_it).ratingCriteriaList.begin();
+ for ( ; rc_it != (*rating_it).ratingCriteriaList.end() || base_rc_it != (*base_rating_it).ratingCriteriaList.end(); ++rc_it, ++base_rc_it ) {
+ check("checking criteria name",(*rc_it).name,(*base_rc_it).name);
+ check("checking stars",(*rc_it).stars,(*base_rc_it).stars);
+ }
+ check( "criteria count", int((*rating_it).ratingCriteriaList.count()), int((*base_rating_it).ratingCriteriaList.count()) );
+ }
+ check( "rating count", int(rating.count()), int(base.count()) );
+}
+
+#endif
diff --git a/krecipes/src/tests/exportertest.h b/krecipes/src/tests/exportertest.h
new file mode 100644
index 0000000..9df5fb8
--- /dev/null
+++ b/krecipes/src/tests/exportertest.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef EXPORTERTEST_H
+#define EXPORTERTEST_H
+
+#include <cmath>
+#include <iostream>
+
+#include <qstring.h>
+#include <qfile.h>
+#include <qtextstream.h>
+
+#include "checks.h"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+void check( BaseExporter &exporter, const RecipeList &recipeList )
+{
+ QFile file("test.txt");
+ if ( file.open( IO_WriteOnly ) ) {
+ QTextStream stream(&file);
+ exporter.writeStream(stream,recipeList);
+ }
+ else {
+ printf("Unable to open file for writing\n");
+ exit(1);
+ }
+
+ file.close();
+}
+
+#endif
diff --git a/krecipes/src/tests/importertest.h b/krecipes/src/tests/importertest.h
new file mode 100644
index 0000000..f8fad78
--- /dev/null
+++ b/krecipes/src/tests/importertest.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef IMPORTERTEST_H
+#define IMPORTERTEST_H
+
+#include <cmath>
+#include <iostream>
+
+#include <qstring.h>
+
+#include "checks.h"
+#include "datablocks/categorytree.h"
+
+using std::cout;
+using std::endl;
+
+void check( const BaseImporter &importer, const Recipe &recipe )
+{
+ int recipe_num = 1;
+ RecipeList recipeList = importer.recipeList();
+ for ( RecipeList::const_iterator it = recipeList.begin(); it != recipeList.end(); ++it ) {
+ printf("Recipe %d... ",recipe_num);
+ check( *it, recipe );
+ printf("successful\n");
+ ++recipe_num;
+ }
+
+ check( "recipe count", recipe_num-1, 2 );
+}
+
+void check( const BaseImporter &importer, const CategoryTree *baseCatStructure )
+{
+ printf("Checking category structure.\n");
+ check( importer.categoryStructure(), baseCatStructure );
+}
+
+#endif
diff --git a/krecipes/src/tests/kretest.cpp b/krecipes/src/tests/kretest.cpp
new file mode 100644
index 0000000..2c739e5
--- /dev/null
+++ b/krecipes/src/tests/kretest.cpp
@@ -0,0 +1,191 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+#include <qfile.h>
+
+#include <iostream>
+
+#include "kreimporter.h"
+#include "kreexporter.h"
+#include "importertest.h"
+#include "exportertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "kretest");
+
+ printf("Creating KreImporter.\n");
+ KreImporter importer;
+
+ printf("Parsing kretest.txt.\n");
+ QStringList files; files << "kretest.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Cookies_Test";
+ recipe.yield.amount = 2;
+ recipe.yield.amount_offset = 1;
+ recipe.yield.type = "dozen";
+ recipe.categoryList.append( Element("Snacks",1) );
+ recipe.categoryList.append( Element("Cookies & Squares",2) );
+ recipe.instructions =
+ "Drop by spoonful on greased cookie sheet. Bake in moderate oven.";
+ recipe.prepTime = QTime(0,30);
+ if ( !recipe.photo.load( "test_photo.jpg", "JPEG" ) ) {
+ printf("Unable to load test_photo.jpg\n");
+ exit(1);
+ }
+
+ recipe.authorList.append( Element("Colleen Beamer") );
+ recipe.authorList.append( Element("Mona Beamer") );
+
+ Ingredient ing;
+ ing.name = "granulated sugar";
+ ing.amount = 0.75;
+ ing.amount_offset = 0.25;
+ ing.units.name = "c.";
+ ing.groupID = 0; ing.group = "Dry Ingredients";
+ recipe.ingList.append( ing );
+
+ Ingredient ing2;
+ ing2.name = "brown sugar";
+ ing2.amount = 1;
+ ing2.amount_offset = 0;
+ ing2.units.name = "c.";
+ ing2.groupID = 0; ing2.group = "Dry Ingredients";
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing3;
+ ing3.name = "all-purpose flour";
+ ing3.amount = 2;
+ ing3.units.plural = "c.";
+ ing3.groupID = 0; ing3.group = "Dry Ingredients";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "baking soda";
+ ing4.amount = 1;
+ ing4.amount_offset = 0;
+ ing4.units.name = "tsp.";
+ ing4.groupID = 0; ing4.group = "Dry Ingredients";
+ recipe.ingList.append( ing4 );
+
+ Ingredient ing8;
+ ing8.name = "shortening";
+ ing8.amount = 1;
+ ing8.amount_offset = 0;
+ ing8.units.name = "c.";
+ ing8.prepMethodList.append( Element("softened") );
+ ing8.prepMethodList.append( Element("at room temperature") );
+ ing8.groupID = 1; ing8.group = "Fat & Liquids";
+ recipe.ingList.append( ing8 );
+
+ Ingredient ing6;
+ ing6.name = "peanut butter";
+ ing6.amount = 1;
+ ing6.amount_offset = 0;
+ ing6.units.name = "c.";
+ ing6.groupID = 1; ing6.group = "Fat & Liquids";
+ recipe.ingList.append( ing6 );
+
+ Ingredient ing5;
+ ing5.name = "eggs";
+ ing5.amount = 2;
+ ing5.amount_offset = 0;
+ ing5.units.plural = "";
+ ing5.groupID = 1; ing5.group = "Fat & Liquids";
+ recipe.ingList.append( ing5 );
+
+ Ingredient ing7;
+ ing7.name = "vanilla extract";
+ ing7.amount = 1;
+ ing7.amount_offset = 0;
+ ing7.units.name = "tsp.";
+ ing7.groupID = 1; ing7.group = "Fat & Liquids";
+ recipe.ingList.append( ing7 );
+
+ Ingredient ing9;
+ ing9.name = "a";
+ ing9.amount = 1;
+ ing9.amount_offset = 0;
+ ing9.units.name = "cup";
+ IngredientData ing9_1;
+ ing9_1.name = "b";
+ ing9_1.amount = 2;
+ ing9_1.amount_offset = 0;
+ ing9_1.units.plural = "cups";
+ IngredientData ing9_2;
+ ing9_2.name = "c";
+ ing9_2.amount = 3;
+ ing9_2.amount_offset = 0;
+ ing9_2.units.plural = "cups";
+ ing9.substitutes.append(ing9_1);
+ ing9.substitutes.append(ing9_2);
+ recipe.ingList.append( ing9 );
+
+ CategoryTree *catTree = new CategoryTree;
+ (void)catTree->add( Element("Cookies & Squares",2) );
+ (void)catTree->add( Element("Snacks",1) );
+
+ RatingCriteria rc;
+ Rating rating1;
+ rating1.rater = "George McFry";
+ rating1.comment = "Good enough";
+
+ rc.name = "Taste";
+ rc.stars = 5.0;
+ rating1.append(rc);
+
+ Rating rating2;
+ rating2.rater = "Me";
+ rating2.comment = "Yuck, don't eat!";
+
+ rc.name = "Overall";
+ rc.stars = 2.0;
+ rating2.append(rc);
+
+ rc.name = "Taste";
+ rc.stars = 1.5;
+ rating2.append(rc);
+
+ recipe.ratingList.append(rating1);
+ recipe.ratingList.append(rating2);
+
+
+ check( importer, recipe );
+ check( importer, catTree );
+
+
+ RecipeList recipeList;
+ recipeList.append(recipe);
+ recipeList.append(recipe);
+
+ printf("Creating KreExporter.\n");
+ KreExporter exporter(catTree,"not needed",".kreml");
+ check( exporter, recipeList );
+ printf("Successfully exported recipes to test.txt.\n");
+
+ printf("Creating KreImporter to test exported recipes.\n");
+ KreImporter importer2;
+
+ printf("Parsing test.txt.\n");
+ QStringList files2; files2 << "test.txt";
+ importer2.parseFiles(files2);
+ QFile::remove("test.txt");
+ check( importer2, recipe );
+ check( importer2, catTree );
+ printf("Recipe export successful.\n");
+
+ printf("*** Krecipes importer and exporter passed the tests :-) ***\n");
+}
diff --git a/krecipes/src/tests/kretest.txt b/krecipes/src/tests/kretest.txt
new file mode 100644
index 0000000..35b4ae6
--- /dev/null
+++ b/krecipes/src/tests/kretest.txt
@@ -0,0 +1,409 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<krecipes version="SVN_PRE-0.9" lang="en_US" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="krecipes.xsd">
+<krecipes-category-structure>
+<category name="Cookies &amp; Squares">
+</category>
+<category name="Snacks">
+</category>
+</krecipes-category-structure>
+<krecipes-recipe>
+<krecipes-description>
+<title>Cookies_Test</title>
+<author>Colleen Beamer</author>
+<author>Mona Beamer</author>
+<pictures>
+<pic format="JPEG" id="1"><![CDATA[/9j/4AAQSkZJRgABAQIAAAAAAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
+HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
+MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACmAIMDASIA
+AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
+AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
+ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
+p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA
+AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx
+BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK
+U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3
+uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD2iyso
+Z7cO6kscg4NWRpluR0b/AL6o0z/jzH1Nc5PcTCeQCVwNx6MfWtZSlzPUzjGPKdH/AGZb+jfnR/Zl
+v6N+dcx9om/57Sf99Gj7RN/z2k/76NRzz7lcsTp/7Lt/Rvzo/sy39G/OuZ+0zAZ81j7FzWXceKdO
+tJXgn1mGOaM/OHnA2/X0o559w5Yndf2Zb+jfnR/Zlv6N/wB9VyVrqiX1ulzaXvn27EfvUkypGecH
+oam8+cZzNJ9Cx/n/AJ60c8+4csTp/wCzLf0b/vqj+zLf0b865j7RNkjzpAR1G40faJv+e0n/AH0a
+OefcOWJ0/wDZlv6N+dH9mW/o351zH2ib/ntJ/wB9Gj7RN/z2k/76NHPPuHLE6f8Asy39G/Oj+zLf
+0b865j7RN/z2k/76NH2ib/ntJ/30aOefcOWJ050y39G/76pP7Mt/Rv8AvquZ+0Tf89pP++jXSaOz
+Pp6sxLHJ5Jo55dw5FbQx5PLSRlA4BopJ/wDXv/vGiu3UxNzTP+PMfU1zE/8Ax8S/75/nXT6Z/wAe
+g+prmJ/+PiX/AHz/ADrin8bNov3SOjHU5wByaKOvHrxUlGR4pu2svDl7KqCRigjxyMbyF6/jmvL4
+YtqooJzyd3fNd748lmGl28aufLmnCygjrgFh/IVyltBuI46UAdn4EsENhdyyxq24heR781LrCyaX
+vlsJPIfnCfwNnGcj8BzW14TtfK8PhsH5yW/WsTxXJstZSDxtOT/h+VJtIFG+xe0bV7bWtNiureVJ
+DjbIsbZ2uOGHODwQRk9R7Vod/ryPpXkXwc1OVr/WrDZF5Jf7QJMHO4tt9emK9d6KCBgMTyOnH/66
+B8rfQKKP5etGecd6buK3YKKO4HrQDkZoE2ugV0+i/wDINX/eNcxXT6L/AMg1f940gMif/Xyf7xoo
+n/18n+8aK70jlubmmf8AHmPqa5ef/j4l/wB8/wA66jTP+PMfU1y8/wDx8S/75/nXHP42dMfhI6Oc
+cfn6UUf0qSjifGUhk1S0iWXciRsxTPfPGfwNV9PtSyZHek1gR3Hie5aJkcDaj7TyGUbT+oro9N0/
+EMZ28HrQB0Mug3OoeFreCzv5rKdIiVaNiMttxzjtmvGvFia9ocbjUdVNyzAkFJWI445yPevoiJlg
+0zOMBEx+lfP/AMUJvPQkD+F+B9V5/Ssaq0PTy+rLmcXroyr8NBr14ZLrSriztdPS5VbuJ87nHBO0
+AYyRxmvSpfFehw3Rt3v41mztKhGOD7HGPxzXmPgn7XYfDTVrlDJD9pul8t8Ab1OFODj6j/CszTtV
+8PWYmOrWct9cOwKnziqoPQAfrnPQVEpuLsjSjhYVqbq1Nr2PdEuYJLf7QkyNDgsZVYFSB15rIHjH
+QS4i/tJd3UAowz29PevKdEvZ9Y1250jTHK297GxETO21duWCrgjGeAfXJrTtdN0fQ3Fn4k0aWaUy
+PtuvMcBlGOFAI3AU3VldMcMDRtJLVnYeK/FjaStq2mSwXDOGfaG3K6DsG7ZIIrT0PxPY61bQkzwR
+XjIPMtvMyyNjlQTjdj2rifGcWkw+DtNfRURLHZKY9rZOOSRk5PDbsjPXNbfgrwroi6LpGtiyX+0D
+AsjXDStnJXDHBOOc+lOnJuTuZ4iFKNKPKtWdvXT6L/yDV/3jXMkd/mPoT0/Sul0b/kGr/vGt2eVf
+cyZ/9fJ/vGiif/Xyf7xoruRym5pn/HmPqa5ef/j4l/3z/Ouo0z/jzH1NcvP/AMfEv++f51xz+NnT
+H4SOkYlVY7ScjGAM5payvE2oS6V4Z1G9gVDLDAzKHBI/Qg/rUlHDR67pKatcSXF2sBad3aOT7yEs
+cggd88V6I+q2VnoYvVWRrdUBLqhOa+TTM7Tea7szM25mY5JOeTnvXumj+N767FpZ2tvbSaLJa7Zy
+4+ZW6Hv9KTaSuwNe4+M/hqa0aAXUyFgQQYm4rmNb0y98V+HJtX0tPPtlUgclWPToD1qtZfCH+0Yp
+ruS+SNZizxoEzgHp/Stu2uPEHh3RrXRLVraAQO3nmRfvx8YI/X9K4oY6hVn7KL1N6M505c0RPhk0
+Ws+BZtFu7ZTFaTPCQx+9uJfJHUEFuMelR6jHr3gOwk/s9oZdOaVpAVC7lJx1DY5PbbnoarfDG+J8
+Y+I7WKX/AELcZtmBgtuwTntx+FUte1K58T6swMmbKEkQRr8oH+0R612TppvQulip04uKV0ULr4h+
+I9TiSLm3i2kM0JCyOQScgjpxgY7496zZtd1ySJ42uNTuAw5EtwWX8ia7DSvCpnI+TJyOw6V2Vl8P
+YpoxuO0+1S6a6FU8ZODvG1zwy0N1FbyQTbvJmcsY9/AJ4Jx68fpXo3w/8Tapcz22jw2ay2MEYDSu
+yI8KAYBABJYE4B9OK1Nf+Hpsoi8QDKB6V5rey3vhy6TUbCUw3Fs2QcfeB42Edwc9KajYmWLnUjyz
+R9CEdCRyR6jgfQf55rptF/5Bq/7xri9G1BNV0a0vY3RxNErsUHG8j5gPYHI/Cu00X/kGr9TVs5DI
+n/18n+8aKJ/9fJ/vGiu5HMbmmf8AHmPqa5ef/j4l/wB8/wA66jTP+PMfU1y8/wDx8S/75/nXHP42
+dEfhI6p6rp0OraVdWNwrNDNGVYKcEj2q5QM5BGOPWpKPka9tJ7C8ntLmMRz27mOVcg7WU4I9Dz6V
+HFdyw/6uR15zgHFegfGLSVsfFYu0dNt7EH2KmMEYBye5J5rzemgPY/AXxDw8dlfylc4XJ6Gu+8X+
+H4fFWhEw488AtE4PKnHb618xpI0bhkJDDkEdq9X+H/xIa3kGnam6lHwFkbtXzWYZXOlU+tYbdbm0
+ZrZlH4eRXGmSeLF3PBcW+my4ZTgxuucEe/GeK6bw1apcXsEMnO/GPQ+4/wDr81tXehWlr48sNdjl
+RLS8VoZ1Y/I7MhVcjvkkD8ar2el3WjavFHOwLK29JFGA4z/9fpXt4TEKvTUlv1M5KzPRk0+KwcIq
+j5QM4rUt7hEUZNZc92s4WUH7wANQGcgHJwRXSSzR1C5WWN0Azxz7V4L8SUjt7OQ7gN8gC4HU16xq
+epRWlrJNPKI0UfMSccV4B4k1a78aeI4rHTojIGk2Qxr/ABH1/IE0Aex/DL/knek8bRiQgf8AbRua
+9U0X/kGr9TXB+GtGGgeHLLTVZmEMeGLHPzElmA9gSa7zRf8AkGr9TQBkT/6+T/eNFE/+vk/3jRXc
+jmNzTP8AjzH1NcvP/wAfEv8Avn+ddRpn/HmPqa5ef/j4l/3z/OuOfxs3j8JHR05xnHOPWjoM8cet
+Z+s6xZ6Fpsl9eSBI06A8lj6D1PtUlHlXxtvoDPp1isUBmVS5kUgugP8ABjsvOfwFeZJo8rWS3O4B
+Sam1jVL3xJrkl/dsJLmdxwgwFHQKB6DgV7b4A+GUN3pVvc6wHZWOVg6A/WgDwy/0prO1jnMgYPg8
+e9O0SwGqaxZ2HnxxCaUAvK21F/HtX11ceANAubL7LJpcAj242qpJH45rxDx58Lx4fkXU9EDvFE+5
+4H5Ckc5yMce1AHpNp4eOnpHBa3zmyjeN4oHjDlNuDjc3PJH4ZrVvLKC9t/ImB2ZypDfMvuD2/wD1
+1n+F9bTxD4cstSUAPLGBKApAVxwwAJPGQcVqyyLDE8jZwiljj0AzWdOnCm24Kw27nJX+pX+gjUHL
+rcwwlViChvNJIBC9OTzwQT271zep/EHxDbySQReFrxJ0O0Exs6g98MAc1s6A03iLUxdTFns4JmuU
+Zx94k/IMjABUbT0Nd1WgjxC78NfEHxjcL9viW2tpE3J5sqhFGMgYUk5/Cu78HfDrTfDCw3cyi51R
+UKmUnKoT12encZ4yCa7SigAwMnGCcD5sYz7V0+i/8g1fqa5iun0X/kGr9TQBkT/6+T/eNFE/+vk/
+3jRXcjmNzTP+PMfU1y8//HxL/vn+ddRpn/HmPqa5e4/4+ZMf3z/OuOfxs3j8JGOXAHTnP5ZrxL4w
+eJftV/HoltKTDbDdOoJAMp7FehwNpz/tGvWPEurx6B4evNTcY8lPlGMgsTheOO5GfbNfLN7dzX15
+Ncztuklcux9z9ako6f4c6SNW8UQGXlITvbP+fevrmLy7LSo1XGMAYFfMfwr2wzyzHGS2K9wl1hpY
+EXfxQB2MV/AYgTgH0rk/EU8dwzggMCMHjtVM6kyrw1Zl5eljkkE4PNAHAWPiFPAHiy4sp0P9jahI
+JWdFyYmPG73A7+3SvQdcubHU9HltYriGWKaPzGKncPLHO788fr6V5P8AEZVls45QOUbt6VJZzHwt
+8N0uZt0lzqEgXY7Y/dDnA9KAPUvBlh9j0FJmiaOa5kaZvnDArn5CCDxlAvFdDVHRpobrQbKe2iVI
+Ht4zGgfOwbRxnuR0/Cr1ABRRRQAV0+i/8g1fqa5iun0X/kGr9TQBkT/6+T/eNFE/+vk/3jRXcjmN
+zTP+PMfU1y8//HxLn+8T+tdRpn/HmPqa5LUruCyS6uriRY4YQ7SOx2gAe9cc/jZvH4Tyv41ahZtp
+VlpvnobtbjzWjxyF2sOT9SP514k3KjGce9dd4ih1rxHDfeLprWQWDzbA552rnC9hlRwufUiuRkzn
+kYNSUdt4Dv8AypXQtjnNeqW+oZjBLDFeBaNfGw1COUfdJwa9Q0/VFkiB3DBoA7Jrvg81SuLvPesr
+7eCvUVTur8KnXr3oAq6rZNrl3b6ep4mmRWPouRn9K574kaql5rUNjbsDb2ibFC9AaivfFU9lczra
+riRlZBKf4cjGa5uxs7rWNWgtoRJLPcOFBA3H3OPzoA+h/hlHLF8PdLWZGVwsrbHB3YaRiDj0IOfx
+zXXVU0u0+waTZ2RcP9mhWJm6btowD7dOmTVugAooooAK6fRf+Qav1NcxXT6L/wAg1fqaAMif/Xyf
+7xoon/18n+8aK7kcxt6Z/wAeY+p4rzTxvpGo6/HHpNo5hs7iZjdziTBjQfwhc87vy4r0zTP+PMfU
+1y8//HxL/vn+dcc/jZvH4TIn0Cxm8OHRPL/0EQCBRIN+wAYU4PXBwR7gV83+K/C2oeFdTa0vEJjb
+JhnH3ZF9vQ+oPNfUfAPt39qy9e0vSdU09k1m2ilt0+YeZnIPqCCKhtRV2Uj5OGQcit/TdaaFFRmI
+ArZ1DwDdLPO9hvkt97eSJR8xQHjJ9cda5jUtJutLkVLiMqXGV96zhXpVHyqWo7HULr8ZXmSqV3rq
+Mv38iuetrdp3CAhcn7zHH4D1rqLPwcktuxnuCZOPuD7v+NFWrCkryYJXE1zXdKufDVlZ2KD7SBi5
+d48MT9cV6n8NfAjeG4Tql7ldTuE2hFbIhjODt9ycD6Y964Lwz4Pht/FunNf3Aax84MMYBZgcopyD
+wWAH4176ORn26g8H8KqnVhNaMT0F4z069j/D6GkoxRVgFFFFABXT6L/yDV+prmK6fRf+Qav1NAGR
+P/r5P940UT/6+T/eNFdyOY3NM/48x9TXLz/8fEv++f510+mf8eY+prJl0W7eV2Hl4LEj5q45/Gze
+PwmV3rm/Gekalq2kImmyBpIn8w27EATEdPmPAI7ZIHNdt/YV5/0z/wC+qX+xLz/pn9N3Ws5RTVmV
+c8igsvF9uqINCkkGMYe5g5H4vxTL/wAH6v4tmt4tRsk0y2hYFmLqzyKc7tuwnGMDrjrXr/8AYd5n
+P7vPTlu1B0O87bM+u7oPTpXLTwdKnPnitR82hxtv4J8PW2j/ANlJpsLW2S2XG5gxyNwY87gDgHqO
+1Yq/DaK2uXey1m6ghPCRMgkCD05r03+wrvsI8f71J/Yd5jGI/wDvqumUIz+JXEmzi7LwVpVpc290
+4muLiHBDSysV3gYLbc4BzzXRkkkkk89a0f7CvB/zz/76o/sK8/6Z/wDfVOMVH4UBm0Vpf2Fef9M/
+++qP7CvP+mf/AH1VXAzaK0v7CvP+mf8A31R/YV5/0z/76ouBm10+i/8AINX6msr+w7z/AKZ/99Vt
+abbva2YikxuBJ4NAGHP/AK+T/eNFE/8Ar35H3jRXcjmLlveSW8YiCKcE8kmpf7XcceUv50UVEoRv
+sXFsP7Yf/nkv50n9sP8A88l/OiilyR7Duw/tiT/nkv50f2xJ/wA8l/Oiijkj2C7D+2JP+eS/nR/b
+En/PJfzooo5I9guw/tiT/nkv50f2xJ/zyX86KKOSPYLsP7Yk/wCeS/nR/bEn/PJfzooo5I9guw/t
+iT/nkv50f2xJ/wA8l/Oiijkj2C7F/th/+eS/nR/a7n/lkv50UUKEewXZmtJudjtHJooorexkf//Z]]></pic>
+</pictures>
+<category>
+<cat>Snacks</cat>
+<cat>Cookies &amp; Squares</cat>
+</category>
+<yield><amount><min>2</min><max>3</max></amount><type>dozen</type></yield>
+<preparation-time>00:30</preparation-time>
+</krecipes-description>
+<krecipes-ingredients>
+<ingredient-group name="Dry Ingredients">
+<ingredient>
+<name>granulated sugar</name>
+<amount><min>0.75</min><max>1</max></amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>brown sugar</name>
+<amount>1</amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>all-purpose flour</name>
+<amount>2</amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>baking soda</name>
+<amount>1</amount>
+<unit>tsp.</unit>
+</ingredient>
+</ingredient-group>
+<ingredient-group name="Fat &amp; Liquids">
+<ingredient>
+<name>shortening</name>
+<amount>1</amount>
+<unit>c.</unit>
+<prep>softened,at room temperature</prep>
+</ingredient>
+<ingredient>
+<name>peanut butter</name>
+<amount>1</amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>eggs</name>
+<amount>2</amount>
+<unit></unit>
+</ingredient>
+<ingredient>
+<name>vanilla extract</name>
+<amount>1</amount>
+<unit>tsp.</unit>
+</ingredient>
+</ingredient-group>
+<ingredient>
+ <name>a</name>
+ <amount>1</amount>
+ <unit>cup</unit>
+ <substitutes>
+ <ingredient>
+ <name>b</name>
+ <amount>2</amount>
+ <unit>cups</unit>
+ </ingredient>
+ <ingredient>
+ <name>c</name>
+ <amount>3</amount>
+ <unit>cups</unit>
+ </ingredient>
+ </substitutes>
+</ingredient>
+</krecipes-ingredients>
+<krecipes-instructions>
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.</krecipes-instructions>
+<krecipes-ratings>
+ <rating>
+ <rater>George McFry</rater>
+ <comment>Good enough</comment>
+ <criterion>
+ <criteria>
+ <name>Taste</name>
+ <stars>5</stars>
+ </criteria>
+ </criterion>
+ </rating>
+ <rating>
+ <rater>Me</rater>
+ <comment>Yuck, don't eat!</comment>
+ <criterion>
+ <criteria>
+ <name>Overall</name>
+ <stars>2</stars>
+ </criteria>
+ <criteria>
+ <name>Taste</name>
+ <stars>1.5</stars>
+ </criteria>
+ </criterion>
+ </rating>
+</krecipes-ratings>
+</krecipes-recipe>
+<krecipes-recipe>
+<krecipes-description>
+<title>Cookies_Test</title>
+<author>Colleen Beamer</author>
+<author>Mona Beamer</author>
+<pictures>
+<pic format="JPEG" id="1"><![CDATA[/9j/4AAQSkZJRgABAQIAAAAAAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a
+HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy
+MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCACmAIMDASIA
+AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
+AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3
+ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
+p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA
+AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx
+BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK
+U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3
+uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD2iyso
+Z7cO6kscg4NWRpluR0b/AL6o0z/jzH1Nc5PcTCeQCVwNx6MfWtZSlzPUzjGPKdH/AGZb+jfnR/Zl
+v6N+dcx9om/57Sf99Gj7RN/z2k/76NRzz7lcsTp/7Lt/Rvzo/sy39G/OuZ+0zAZ81j7FzWXceKdO
+tJXgn1mGOaM/OHnA2/X0o559w5Yndf2Zb+jfnR/Zlv6N/wB9VyVrqiX1ulzaXvn27EfvUkypGecH
+oam8+cZzNJ9Cx/n/AJ60c8+4csTp/wCzLf0b/vqj+zLf0b865j7RNkjzpAR1G40faJv+e0n/AH0a
+OefcOWJ0/wDZlv6N+dH9mW/o351zH2ib/ntJ/wB9Gj7RN/z2k/76NHPPuHLE6f8Asy39G/Oj+zLf
+0b865j7RN/z2k/76NH2ib/ntJ/30aOefcOWJ050y39G/76pP7Mt/Rv8AvquZ+0Tf89pP++jXSaOz
+Pp6sxLHJ5Jo55dw5FbQx5PLSRlA4BopJ/wDXv/vGiu3UxNzTP+PMfU1zE/8Ax8S/75/nXT6Z/wAe
+g+prmJ/+PiX/AHz/ADrin8bNov3SOjHU5wByaKOvHrxUlGR4pu2svDl7KqCRigjxyMbyF6/jmvL4
+YtqooJzyd3fNd748lmGl28aufLmnCygjrgFh/IVyltBuI46UAdn4EsENhdyyxq24heR781LrCyaX
+vlsJPIfnCfwNnGcj8BzW14TtfK8PhsH5yW/WsTxXJstZSDxtOT/h+VJtIFG+xe0bV7bWtNiureVJ
+DjbIsbZ2uOGHODwQRk9R7Vod/ryPpXkXwc1OVr/WrDZF5Jf7QJMHO4tt9emK9d6KCBgMTyOnH/66
+B8rfQKKP5etGecd6buK3YKKO4HrQDkZoE2ugV0+i/wDINX/eNcxXT6L/AMg1f940gMif/Xyf7xoo
+n/18n+8aK70jlubmmf8AHmPqa5ef/j4l/wB8/wA66jTP+PMfU1y8/wDx8S/75/nXHP42dMfhI6Oc
+cfn6UUf0qSjifGUhk1S0iWXciRsxTPfPGfwNV9PtSyZHek1gR3Hie5aJkcDaj7TyGUbT+oro9N0/
+EMZ28HrQB0Mug3OoeFreCzv5rKdIiVaNiMttxzjtmvGvFia9ocbjUdVNyzAkFJWI445yPevoiJlg
+0zOMBEx+lfP/AMUJvPQkD+F+B9V5/Ssaq0PTy+rLmcXroyr8NBr14ZLrSriztdPS5VbuJ87nHBO0
+AYyRxmvSpfFehw3Rt3v41mztKhGOD7HGPxzXmPgn7XYfDTVrlDJD9pul8t8Ab1OFODj6j/CszTtV
+8PWYmOrWct9cOwKnziqoPQAfrnPQVEpuLsjSjhYVqbq1Nr2PdEuYJLf7QkyNDgsZVYFSB15rIHjH
+QS4i/tJd3UAowz29PevKdEvZ9Y1250jTHK297GxETO21duWCrgjGeAfXJrTtdN0fQ3Fn4k0aWaUy
+PtuvMcBlGOFAI3AU3VldMcMDRtJLVnYeK/FjaStq2mSwXDOGfaG3K6DsG7ZIIrT0PxPY61bQkzwR
+XjIPMtvMyyNjlQTjdj2rifGcWkw+DtNfRURLHZKY9rZOOSRk5PDbsjPXNbfgrwroi6LpGtiyX+0D
+AsjXDStnJXDHBOOc+lOnJuTuZ4iFKNKPKtWdvXT6L/yDV/3jXMkd/mPoT0/Sul0b/kGr/vGt2eVf
+cyZ/9fJ/vGiif/Xyf7xoruRym5pn/HmPqa5ef/j4l/3z/Ouo0z/jzH1NcvP/AMfEv++f51xz+NnT
+H4SOkYlVY7ScjGAM5payvE2oS6V4Z1G9gVDLDAzKHBI/Qg/rUlHDR67pKatcSXF2sBad3aOT7yEs
+cggd88V6I+q2VnoYvVWRrdUBLqhOa+TTM7Tea7szM25mY5JOeTnvXumj+N767FpZ2tvbSaLJa7Zy
+4+ZW6Hv9KTaSuwNe4+M/hqa0aAXUyFgQQYm4rmNb0y98V+HJtX0tPPtlUgclWPToD1qtZfCH+0Yp
+ruS+SNZizxoEzgHp/Stu2uPEHh3RrXRLVraAQO3nmRfvx8YI/X9K4oY6hVn7KL1N6M505c0RPhk0
+Ws+BZtFu7ZTFaTPCQx+9uJfJHUEFuMelR6jHr3gOwk/s9oZdOaVpAVC7lJx1DY5PbbnoarfDG+J8
+Y+I7WKX/AELcZtmBgtuwTntx+FUte1K58T6swMmbKEkQRr8oH+0R612TppvQulip04uKV0ULr4h+
+I9TiSLm3i2kM0JCyOQScgjpxgY7496zZtd1ySJ42uNTuAw5EtwWX8ia7DSvCpnI+TJyOw6V2Vl8P
+YpoxuO0+1S6a6FU8ZODvG1zwy0N1FbyQTbvJmcsY9/AJ4Jx68fpXo3w/8Tapcz22jw2ay2MEYDSu
+yI8KAYBABJYE4B9OK1Nf+Hpsoi8QDKB6V5rey3vhy6TUbCUw3Fs2QcfeB42Edwc9KajYmWLnUjyz
+R9CEdCRyR6jgfQf55rptF/5Bq/7xri9G1BNV0a0vY3RxNErsUHG8j5gPYHI/Cu00X/kGr9TVs5DI
+n/18n+8aKJ/9fJ/vGiu5HMbmmf8AHmPqa5ef/j4l/wB8/wA66jTP+PMfU1y8/wDx8S/75/nXHP42
+dEfhI6p6rp0OraVdWNwrNDNGVYKcEj2q5QM5BGOPWpKPka9tJ7C8ntLmMRz27mOVcg7WU4I9Dz6V
+HFdyw/6uR15zgHFegfGLSVsfFYu0dNt7EH2KmMEYBye5J5rzemgPY/AXxDw8dlfylc4XJ6Gu+8X+
+H4fFWhEw488AtE4PKnHb618xpI0bhkJDDkEdq9X+H/xIa3kGnam6lHwFkbtXzWYZXOlU+tYbdbm0
+ZrZlH4eRXGmSeLF3PBcW+my4ZTgxuucEe/GeK6bw1apcXsEMnO/GPQ+4/wDr81tXehWlr48sNdjl
+RLS8VoZ1Y/I7MhVcjvkkD8ar2el3WjavFHOwLK29JFGA4z/9fpXt4TEKvTUlv1M5KzPRk0+KwcIq
+j5QM4rUt7hEUZNZc92s4WUH7wANQGcgHJwRXSSzR1C5WWN0Azxz7V4L8SUjt7OQ7gN8gC4HU16xq
+epRWlrJNPKI0UfMSccV4B4k1a78aeI4rHTojIGk2Qxr/ABH1/IE0Aex/DL/knek8bRiQgf8AbRua
+9U0X/kGr9TXB+GtGGgeHLLTVZmEMeGLHPzElmA9gSa7zRf8AkGr9TQBkT/6+T/eNFE/+vk/3jRXc
+jmNzTP8AjzH1NcvP/wAfEv8Avn+ddRpn/HmPqa5ef/j4l/3z/OuOfxs3j8JHR05xnHOPWjoM8cet
+Z+s6xZ6Fpsl9eSBI06A8lj6D1PtUlHlXxtvoDPp1isUBmVS5kUgugP8ABjsvOfwFeZJo8rWS3O4B
+Sam1jVL3xJrkl/dsJLmdxwgwFHQKB6DgV7b4A+GUN3pVvc6wHZWOVg6A/WgDwy/0prO1jnMgYPg8
+e9O0SwGqaxZ2HnxxCaUAvK21F/HtX11ceANAubL7LJpcAj242qpJH45rxDx58Lx4fkXU9EDvFE+5
+4H5Ckc5yMce1AHpNp4eOnpHBa3zmyjeN4oHjDlNuDjc3PJH4ZrVvLKC9t/ImB2ZypDfMvuD2/wD1
+1n+F9bTxD4cstSUAPLGBKApAVxwwAJPGQcVqyyLDE8jZwiljj0AzWdOnCm24Kw27nJX+pX+gjUHL
+rcwwlViChvNJIBC9OTzwQT271zep/EHxDbySQReFrxJ0O0Exs6g98MAc1s6A03iLUxdTFns4JmuU
+Zx94k/IMjABUbT0Nd1WgjxC78NfEHxjcL9viW2tpE3J5sqhFGMgYUk5/Cu78HfDrTfDCw3cyi51R
+UKmUnKoT12encZ4yCa7SigAwMnGCcD5sYz7V0+i/8g1fqa5iun0X/kGr9TQBkT/6+T/eNFE/+vk/
+3jRXcjmNzTP+PMfU1y8//HxL/vn+ddRpn/HmPqa5e4/4+ZMf3z/OuOfxs3j8JGOXAHTnP5ZrxL4w
+eJftV/HoltKTDbDdOoJAMp7FehwNpz/tGvWPEurx6B4evNTcY8lPlGMgsTheOO5GfbNfLN7dzX15
+Ncztuklcux9z9ako6f4c6SNW8UQGXlITvbP+fevrmLy7LSo1XGMAYFfMfwr2wzyzHGS2K9wl1hpY
+EXfxQB2MV/AYgTgH0rk/EU8dwzggMCMHjtVM6kyrw1Zl5eljkkE4PNAHAWPiFPAHiy4sp0P9jahI
+JWdFyYmPG73A7+3SvQdcubHU9HltYriGWKaPzGKncPLHO788fr6V5P8AEZVls45QOUbt6VJZzHwt
+8N0uZt0lzqEgXY7Y/dDnA9KAPUvBlh9j0FJmiaOa5kaZvnDArn5CCDxlAvFdDVHRpobrQbKe2iVI
+Ht4zGgfOwbRxnuR0/Cr1ABRRRQAV0+i/8g1fqa5iun0X/kGr9TQBkT/6+T/eNFE/+vk/3jRXcjmN
+zTP+PMfU1y8//HxLn+8T+tdRpn/HmPqa5LUruCyS6uriRY4YQ7SOx2gAe9cc/jZvH4Tyv41ahZtp
+VlpvnobtbjzWjxyF2sOT9SP514k3KjGce9dd4ih1rxHDfeLprWQWDzbA552rnC9hlRwufUiuRkzn
+kYNSUdt4Dv8AypXQtjnNeqW+oZjBLDFeBaNfGw1COUfdJwa9Q0/VFkiB3DBoA7Jrvg81SuLvPesr
+7eCvUVTur8KnXr3oAq6rZNrl3b6ep4mmRWPouRn9K574kaql5rUNjbsDb2ibFC9AaivfFU9lczra
+riRlZBKf4cjGa5uxs7rWNWgtoRJLPcOFBA3H3OPzoA+h/hlHLF8PdLWZGVwsrbHB3YaRiDj0IOfx
+zXXVU0u0+waTZ2RcP9mhWJm6btowD7dOmTVugAooooAK6fRf+Qav1NcxXT6L/wAg1fqaAMif/Xyf
+7xoon/18n+8aK7kcxt6Z/wAeY+p4rzTxvpGo6/HHpNo5hs7iZjdziTBjQfwhc87vy4r0zTP+PMfU
+1y8//HxL/vn+dcc/jZvH4TIn0Cxm8OHRPL/0EQCBRIN+wAYU4PXBwR7gV83+K/C2oeFdTa0vEJjb
+JhnH3ZF9vQ+oPNfUfAPt39qy9e0vSdU09k1m2ilt0+YeZnIPqCCKhtRV2Uj5OGQcit/TdaaFFRmI
+ArZ1DwDdLPO9hvkt97eSJR8xQHjJ9cda5jUtJutLkVLiMqXGV96zhXpVHyqWo7HULr8ZXmSqV3rq
+Mv38iuetrdp3CAhcn7zHH4D1rqLPwcktuxnuCZOPuD7v+NFWrCkryYJXE1zXdKufDVlZ2KD7SBi5
+d48MT9cV6n8NfAjeG4Tql7ldTuE2hFbIhjODt9ycD6Y964Lwz4Pht/FunNf3Aax84MMYBZgcopyD
+wWAH4176ORn26g8H8KqnVhNaMT0F4z069j/D6GkoxRVgFFFFABXT6L/yDV+prmK6fRf+Qav1NAGR
+P/r5P940UT/6+T/eNFdyOY3NM/48x9TXLz/8fEv++f510+mf8eY+prJl0W7eV2Hl4LEj5q45/Gze
+PwmV3rm/Gekalq2kImmyBpIn8w27EATEdPmPAI7ZIHNdt/YV5/0z/wC+qX+xLz/pn9N3Ws5RTVmV
+c8igsvF9uqINCkkGMYe5g5H4vxTL/wAH6v4tmt4tRsk0y2hYFmLqzyKc7tuwnGMDrjrXr/8AYd5n
+P7vPTlu1B0O87bM+u7oPTpXLTwdKnPnitR82hxtv4J8PW2j/ANlJpsLW2S2XG5gxyNwY87gDgHqO
+1Yq/DaK2uXey1m6ghPCRMgkCD05r03+wrvsI8f71J/Yd5jGI/wDvqumUIz+JXEmzi7LwVpVpc290
+4muLiHBDSysV3gYLbc4BzzXRkkkkk89a0f7CvB/zz/76o/sK8/6Z/wDfVOMVH4UBm0Vpf2Fef9M/
+++qP7CvP+mf/AH1VXAzaK0v7CvP+mf8A31R/YV5/0z/76ouBm10+i/8AINX6msr+w7z/AKZ/99Vt
+abbva2YikxuBJ4NAGHP/AK+T/eNFE/8Ar35H3jRXcjmLlveSW8YiCKcE8kmpf7XcceUv50UVEoRv
+sXFsP7Yf/nkv50n9sP8A88l/OiilyR7Duw/tiT/nkv50f2xJ/wA8l/Oiijkj2C7D+2JP+eS/nR/b
+En/PJfzooo5I9guw/tiT/nkv50f2xJ/zyX86KKOSPYLsP7Yk/wCeS/nR/bEn/PJfzooo5I9guw/t
+iT/nkv50f2xJ/wA8l/Oiijkj2C7F/th/+eS/nR/a7n/lkv50UUKEewXZmtJudjtHJooorexkf//Z]]></pic>
+</pictures>
+<category>
+<cat>Snacks</cat>
+<cat>Cookies &amp; Squares</cat>
+</category>
+<yield><amount><min>2</min><max>3</max></amount><type>dozen</type></yield>
+<preparation-time>00:30</preparation-time>
+</krecipes-description>
+<krecipes-ingredients>
+<ingredient-group name="Dry Ingredients">
+<ingredient>
+<name>granulated sugar</name>
+<amount><min>0.75</min><max>1</max></amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>brown sugar</name>
+<amount>1</amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>all-purpose flour</name>
+<amount>2</amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>baking soda</name>
+<amount>1</amount>
+<unit>tsp.</unit>
+</ingredient>
+</ingredient-group>
+<ingredient-group name="Fat &amp; Liquids">
+<ingredient>
+<name>shortening</name>
+<amount>1</amount>
+<unit>c.</unit>
+<prep>softened,at room temperature</prep>
+</ingredient>
+<ingredient>
+<name>peanut butter</name>
+<amount>1</amount>
+<unit>c.</unit>
+</ingredient>
+<ingredient>
+<name>eggs</name>
+<amount>2</amount>
+<unit></unit>
+</ingredient>
+<ingredient>
+<name>vanilla extract</name>
+<amount>1</amount>
+<unit>tsp.</unit>
+</ingredient>
+</ingredient-group>
+<ingredient>
+ <name>a</name>
+ <amount>1</amount>
+ <unit>cup</unit>
+ <substitutes>
+ <ingredient>
+ <name>b</name>
+ <amount>2</amount>
+ <unit>cups</unit>
+ </ingredient>
+ <ingredient>
+ <name>c</name>
+ <amount>3</amount>
+ <unit>cups</unit>
+ </ingredient>
+ </substitutes>
+</ingredient>
+</krecipes-ingredients>
+<krecipes-instructions>
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.</krecipes-instructions>
+<krecipes-ratings>
+ <rating>
+ <rater>George McFry</rater>
+ <comment>Good enough</comment>
+ <criterion>
+ <criteria>
+ <name>Taste</name>
+ <stars>5</stars>
+ </criteria>
+ </criterion>
+ </rating>
+ <rating>
+ <rater>Me</rater>
+ <comment>Yuck, don't eat!</comment>
+ <criterion>
+ <criteria>
+ <name>Overall</name>
+ <stars>2</stars>
+ </criteria>
+ <criteria>
+ <name>Taste</name>
+ <stars>1.5</stars>
+ </criteria>
+ </criterion>
+ </rating>
+</krecipes-ratings>
+</krecipes-recipe>
+</krecipes>
diff --git a/krecipes/src/tests/mmftest.cpp b/krecipes/src/tests/mmftest.cpp
new file mode 100644
index 0000000..f79c4a8
--- /dev/null
+++ b/krecipes/src/tests/mmftest.cpp
@@ -0,0 +1,139 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::endl;
+
+#include "mmfimporter.h"
+#include "mmfexporter.h"
+#include "importertest.h"
+#include "exportertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "mmftest");
+
+ printf("Creating MMFImporter.\n");
+ MMFImporter importer;
+
+ printf("Parsing mmftest.txt.\n");
+ QStringList files; files << "mmftest.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Cookies_Test";
+ recipe.yield.amount = 2;
+ recipe.yield.type = "servings";
+ recipe.categoryList.append( Element("Snacks",1) );
+ recipe.categoryList.append( Element("Cookies & Squares",2) );
+ recipe.instructions =
+ "Drop by spoonful on greased cookie sheet. Bake in moderate oven.";
+ recipe.prepTime = QTime(0,30);
+
+ Ingredient ing9;
+ ing9.name = "a";
+ ing9.amount = 1;
+ ing9.amount_offset = 0;
+ ing9.units.name = "cup";
+ IngredientData ing9_1;
+ ing9_1.name = "b";
+ ing9_1.amount = 2;
+ ing9_1.amount_offset = 0;
+ ing9_1.units.plural = "cups";
+ IngredientData ing9_2;
+ ing9_2.name = "c";
+ ing9_2.amount = 3;
+ ing9_2.amount_offset = 0;
+ ing9_2.units.plural = "cups";
+ ing9.substitutes.append(ing9_1);
+ ing9.substitutes.append(ing9_2);
+ recipe.ingList.append( ing9 );
+
+ Ingredient ing2;
+ ing2.name = "c. granulated sugar";
+ ing2.amount = 0.75;
+ ing2.groupID = 0; ing2.group = "Dry Ingredients";
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing;
+ ing.name = "c. brown sugar";
+ ing.amount = 1;
+ ing.amount_offset = 0;
+ ing.groupID = 0; ing.group = "Dry Ingredients";
+ recipe.ingList.append( ing );
+
+ Ingredient ing3;
+ ing3.name = "c. all-purpose flour";
+ ing3.amount = 2;
+ ing3.groupID = 0; ing3.group = "Dry Ingredients";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "tsp. baking soda";
+ ing4.amount = 1;
+ ing4.groupID = 0; ing4.group = "Dry Ingredients";
+ recipe.ingList.append( ing4 );
+
+ Ingredient ing8;
+ ing8.name = "c. shortening";
+ ing8.amount = 1;
+ ing8.prepMethodList.append( Element("softened") );
+ ing8.prepMethodList.append( Element("at room temperature") );
+ ing8.groupID = 1; ing8.group = "Fat & Liquids";
+ recipe.ingList.append( ing8 );
+
+ Ingredient ing6;
+ ing6.name = "c. peanut butter";
+ ing6.amount = 1;
+ ing6.groupID = 1; ing6.group = "Fat & Liquids";
+ recipe.ingList.append( ing6 );
+
+ Ingredient ing5;
+ ing5.name = "eggs";
+ ing5.amount = 2;
+ ing5.groupID = 1; ing5.group = "Fat & Liquids";
+ recipe.ingList.append( ing5 );
+
+ Ingredient ing7;
+ ing7.name = "tsp. vanilla extract";
+ ing7.amount = 1;
+ ing7.groupID = 1; ing7.group = "Fat & Liquids";
+ recipe.ingList.append( ing7 );
+
+
+ check( importer, recipe );
+
+ RecipeList recipeList;
+ recipeList.append(recipe);
+ recipeList.append(recipe);
+
+ printf("Creating MMFExporter.\n");
+ MMFExporter exporter("not needed",".mmf");
+ check( exporter, recipeList );
+ printf("Successfully exported recipes to test.txt.\n");
+
+ printf("Creating MMFImporter to test exported recipes.\n");
+ MMFImporter importer2;
+
+ printf("Parsing test.txt.\n");
+ QStringList files2; files2 << "test.txt";
+ importer2.parseFiles(files2);
+ QFile::remove("test.txt");
+ check( importer2, recipe );
+ printf("Recipe export successful.\n");
+
+ printf("*** MM format importer and exporter passed the tests :-) ***\n");
+}
diff --git a/krecipes/src/tests/mmftest.txt b/krecipes/src/tests/mmftest.txt
new file mode 100644
index 0000000..9d13af3
--- /dev/null
+++ b/krecipes/src/tests/mmftest.txt
@@ -0,0 +1,49 @@
+----- Exported by Krecipes vSVN_PRE-0.9 [Meal-Master Export Format] -----
+
+ Title: Cookies_Test
+ Categories: Snacks, Cookies & Squares
+ Servings: 2
+
+ 1 c a, or
+ 2 c b, or
+ 3 c c
+------------------------------Dry Ingredients-------------------------------
+ 3/4 c. granulated sugar
+ 1 c. brown sugar
+ 2 c. all-purpose flour
+ 1 tsp. baking soda
+-------------------------------Fat & Liquids--------------------------------
+ 1 c. shortening; softened, at
+ -room temperature
+ 1 c. peanut butter
+ 2 eggs
+ 1 tsp. vanilla extract
+
+ Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+
+-----
+
+----- Exported by Krecipes vSVN_PRE-0.9 [Meal-Master Export Format] -----
+
+ Title: Cookies_Test
+ Categories: Snacks, Cookies & Squares
+ Servings: 2
+
+ 1 c a, or
+ 2 c b, or
+ 3 c c
+------------------------------Dry Ingredients-------------------------------
+ 3/4 c. granulated sugar
+ 1 c. brown sugar
+ 2 c. all-purpose flour
+ 1 tsp. baking soda
+-------------------------------Fat & Liquids--------------------------------
+ 1 c. shortening; softened, at
+ -room temperature
+ 1 c. peanut butter
+ 2 eggs
+ 1 tsp. vanilla extract
+
+ Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+
+-----
diff --git a/krecipes/src/tests/mx2test.cpp b/krecipes/src/tests/mx2test.cpp
new file mode 100644
index 0000000..71f45eb
--- /dev/null
+++ b/krecipes/src/tests/mx2test.cpp
@@ -0,0 +1,72 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::endl;
+
+#include "mx2importer.h"
+#include "importertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "mx2test");
+
+ printf("Creating MX2Importer.\n");
+ MX2Importer importer;
+
+ printf("Parsing mx2test.txt.\n");
+ QStringList files; files << "mx2test.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Title 1";
+ recipe.yield.amount = 2;
+ recipe.yield.type = "servings";
+ recipe.categoryList.append( Element("Category 1") );
+ recipe.categoryList.append( Element("Category 2") );
+ recipe.instructions =
+ "Instruction line 1\n"
+ "Instruction line 2\n"
+ "Instruction line 3";
+
+ Ingredient ing;
+ ing.name = "ingredient 1";
+ ing.amount = 1;
+ ing.units.name = "teaspoon";
+ recipe.ingList.append( ing );
+
+ Ingredient ing2;
+ ing2.name = "ingredient 2";
+ ing2.amount = 3.5;
+ ing2.units.plural = QString::null;
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing3;
+ ing3.name = "ingredient 3";
+ ing3.amount = 3.5;
+ ing3.units.plural = "ounces";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "ingredient 4";
+ ing4.amount = 3.5;
+ ing4.units.plural = "ounces";
+ recipe.ingList.append( ing4 );
+
+ check( importer, recipe );
+
+ printf("Done.\n");
+}
diff --git a/krecipes/src/tests/mx2test.txt b/krecipes/src/tests/mx2test.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/krecipes/src/tests/mx2test.txt
diff --git a/krecipes/src/tests/mxptest.cpp b/krecipes/src/tests/mxptest.cpp
new file mode 100644
index 0000000..0cbfb90
--- /dev/null
+++ b/krecipes/src/tests/mxptest.cpp
@@ -0,0 +1,72 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::endl;
+
+#include "mxpimporter.h"
+#include "importertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "mxptest");
+
+ printf("Creating MXPImporter.\n");
+ MXPImporter importer;
+
+ printf("Parsing mxptest.txt.\n");
+ QStringList files; files << "mxptest.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Title 1";
+ recipe.yield.amount = 2;
+ recipe.yield.type = "servings";
+ recipe.categoryList.append( Element("Category 1") );
+ recipe.categoryList.append( Element("Category 2") );
+ recipe.instructions =
+ "Instruction line 1\n"
+ "Instruction line 2\n"
+ "Instruction line 3";
+
+ Ingredient ing;
+ ing.name = "ingredient 1";
+ ing.amount = 1;
+ ing.units.name = "teaspoon";
+ recipe.ingList.append( ing );
+
+ Ingredient ing2;
+ ing2.name = "ingredient 2";
+ ing2.amount = 3.5;
+ ing2.units.plural = QString::null;
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing3;
+ ing3.name = "ingredient 3";
+ ing3.amount = 3.5;
+ ing3.units.plural = "ounces";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "ingredient 4";
+ ing4.amount = 3.5;
+ ing4.units.plural = "ounces";
+ recipe.ingList.append( ing4 );
+
+ check( importer, recipe );
+
+ printf("Done.\n");
+}
diff --git a/krecipes/src/tests/mxptest.txt b/krecipes/src/tests/mxptest.txt
new file mode 100644
index 0000000..46154c0
--- /dev/null
+++ b/krecipes/src/tests/mxptest.txt
@@ -0,0 +1,25 @@
+ ----- Exported by Krecipes vSVN_PRE-0.9 [Master Cook Export Format] -----
+
+ Cookies Test
+
+Recipe By : Mona Beamer, Colleen Beamer
+Serving Size : 2 Preparation Time :0:45
+Categories : Snacks Cookies & Squares
+
+
+ Amount Measure Ingredient -- Preparation Method
+-------- ------------ --------------------------------
+ 3/4 c. granulated sugar
+ 1 c. brown sugar
+ 2 c. all-purpose flour
+ 1 tsp. baking soda
+ 1 c. shortening -- softened,at room temperature
+ 1 c. peanut butter
+ 2 eggs
+ 1 tsp. vanilla extract
+
+
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+
+Nutr. Assoc. : 0 1374 1021 927 0 1638 1358 797 0 0 0 568 0 532 1611
+
diff --git a/krecipes/src/tests/nyctest.cpp b/krecipes/src/tests/nyctest.cpp
new file mode 100644
index 0000000..70f9460
--- /dev/null
+++ b/krecipes/src/tests/nyctest.cpp
@@ -0,0 +1,100 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::endl;
+
+#include "nycgenericimporter.h"
+#include "importertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "nyctest");
+
+ printf("Creating NYCGenericImporter.\n");
+ NYCGenericImporter importer;
+
+ printf("Parsing nyctest.txt.\n");
+ QStringList files; files << "nyctest.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Cookies_Test";
+ recipe.yield.amount = 2;
+ recipe.yield.type = "dozen";
+ recipe.categoryList.append( Element("Snacks",1) );
+ recipe.categoryList.append( Element("Cookies & Squares",2) );
+ recipe.instructions =
+ "Drop by spoonful on greased cookie sheet. Bake in moderate oven.";
+ //recipe.prepTime = QTime(0,30);
+
+ recipe.authorList.append( Element("Colleen Beamer") );
+
+ Ingredient ing;
+ ing.name = "granulated sugar";
+ ing.amount = 0.75;
+ ing.units.name = "c.";
+ recipe.ingList.append( ing );
+
+ Ingredient ing2;
+ ing2.name = "brown sugar";
+ ing2.amount = 1;
+ ing2.units.name = "c.";
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing3;
+ ing3.name = "all-purpose flour";
+ ing3.amount = 2;
+ ing3.units.plural = "c.";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "baking soda";
+ ing4.amount = 1;
+ ing4.units.name = "tsp.";
+ recipe.ingList.append( ing4 );
+
+ Ingredient ing8;
+ ing8.name = "shortening";
+ ing8.amount = 1;
+ ing8.units.name = "c.";
+ ing8.prepMethodList.append( Element("softened") );
+ ing8.prepMethodList.append( Element("at room temperature") );
+ recipe.ingList.append( ing8 );
+
+ Ingredient ing6;
+ ing6.name = "peanut butter";
+ ing6.amount = 1;
+ ing6.units.name = "c.";
+ recipe.ingList.append( ing6 );
+
+ Ingredient ing5;
+ ing5.name = "eggs";
+ ing5.amount = 2;
+ ing5.units.plural = "whole";
+ recipe.ingList.append( ing5 );
+
+ Ingredient ing7;
+ ing7.name = "vanilla extract";
+ ing7.amount = 1;
+ ing7.units.name = "tsp.";
+
+ recipe.ingList.append( ing7 );
+
+ check( importer, recipe );
+
+ printf("Done.\n");
+}
diff --git a/krecipes/src/tests/nyctest.txt b/krecipes/src/tests/nyctest.txt
new file mode 100644
index 0000000..ad0720b
--- /dev/null
+++ b/krecipes/src/tests/nyctest.txt
@@ -0,0 +1,57 @@
+@@@@@ Exported by Krecipes vSVN_PRE-0.9 [Now You're Cooking! Export Format]
+
+Cookies_Test
+
+Snacks, Cookies & Squares
+
+3/4 c. granulated sugar
+1 c. brown sugar
+2 c. all-purpose flour
+1 tsp. baking soda
+1 c. shortening -- softened,at room temperature
+1 c. peanut butter
+2 whole eggs
+1 tsp. vanilla extract
+
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+
+NYC Nutrition Analysis (per serving or yield unit): water=70.33 g; calories=394.5; protein=3.14 g; total fat=18 g; carbohydrate=56.94 g; dietary fiber=3.57 g; ash=1.18 g; calcium=14.36 mg; phosphorus=37.48 mg; iron=1.59 mg; sodium=339.7 mg; potassium=111.7 mg; magnesium=10.26 mg; zinc=0.25 mg; copper=0.08 mg; manganese=0.32 mg; vitamin A=160.3 IU; vitamin E=1.43 mg ATE; thiamin=0.2 mg; riboflavin=0.14 mg; niacin=1.6 mg; pantothenic acid=0.13 mg; vitamin B6=0.04 mg; folate=35.9 ug; vitamin C=4.13 mg; saturated fat=5.77 g; monounsaturated fat=6.83 g; polyunsaturated fat=4.41 g; caffeine=1.16 mg; selenium=10.13 ug; refuse=3.34%; %cal as carb:prot:fat=57:3:40; WW Pts=8.7; (complete analysis)
+
+Contributor: Colleen Beamer
+
+Yield: 2 dozen
+
+NYC Nutrilink: N5504^19335,N193^02021,N182^02010,N197^02025
+NYC Nutrilink: N218^02047,N5662^20081,N3906^14355,N1896^09153
+NYC Nutrilink: N1766^09003,N599^04522,U3^18402
+
+** Exported from Now You're Cooking! v5.64 **
+
+@@@@@ Exported by Krecipes vSVN_PRE-0.9 [Now You're Cooking! Export Format]
+
+Cookies_Test
+
+Snacks, Cookies & Squares
+
+3/4 c. granulated sugar
+1 c. brown sugar
+2 c. all-purpose flour
+1 tsp. baking soda
+1 c. shortening -- softened,at room temperature
+1 c. peanut butter
+2 whole eggs
+1 tsp. vanilla extract
+
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+
+NYC Nutrition Analysis (per serving or yield unit): water=70.33 g; calories=394.5; protein=3.14 g; total fat=18 g; carbohydrate=56.94 g; dietary fiber=3.57 g; ash=1.18 g; calcium=14.36 mg; phosphorus=37.48 mg; iron=1.59 mg; sodium=339.7 mg; potassium=111.7 mg; magnesium=10.26 mg; zinc=0.25 mg; copper=0.08 mg; manganese=0.32 mg; vitamin A=160.3 IU; vitamin E=1.43 mg ATE; thiamin=0.2 mg; riboflavin=0.14 mg; niacin=1.6 mg; pantothenic acid=0.13 mg; vitamin B6=0.04 mg; folate=35.9 ug; vitamin C=4.13 mg; saturated fat=5.77 g; monounsaturated fat=6.83 g; polyunsaturated fat=4.41 g; caffeine=1.16 mg; selenium=10.13 ug; refuse=3.34%; %cal as carb:prot:fat=57:3:40; WW Pts=8.7; (complete analysis)
+
+Contributor: Colleen Beamer
+
+Yield: 2 dozen
+
+NYC Nutrilink: N5504^19335,N193^02021,N182^02010,N197^02025
+NYC Nutrilink: N218^02047,N5662^20081,N3906^14355,N1896^09153
+NYC Nutrilink: N1766^09003,N599^04522,U3^18402
+
+** Exported from Now You're Cooking! v5.64 ** \ No newline at end of file
diff --git a/krecipes/src/tests/recipemltest.cpp b/krecipes/src/tests/recipemltest.cpp
new file mode 100644
index 0000000..30c8a9b
--- /dev/null
+++ b/krecipes/src/tests/recipemltest.cpp
@@ -0,0 +1,158 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::endl;
+
+#include "recipemlimporter.h"
+#include "recipemlexporter.h"
+#include "importertest.h"
+#include "exportertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "recipemltest");
+
+ printf("Creating RecipeMLImporter.\n");
+ RecipeMLImporter importer;
+
+ printf("Parsing recipemltest.txt.\n");
+ QStringList files; files << "recipemltest.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Cookies_Test";
+ recipe.yield.amount = 2;
+ recipe.yield.amount_offset = 1;
+ recipe.yield.type = "dozen";
+ recipe.categoryList.append( Element("Snacks",1) );
+ recipe.categoryList.append( Element("Cookies & Squares",2) );
+ recipe.instructions =
+ "Drop by spoonful on greased cookie sheet. Bake in moderate oven.";
+ recipe.prepTime = QTime(0,30);
+
+ recipe.authorList.append( Element("Colleen Beamer") );
+ recipe.authorList.append( Element("Mona Beamer") );
+
+ Ingredient ing;
+ ing.name = "granulated sugar";
+ ing.amount = 0.75;
+ ing.amount_offset = 0.25;
+ ing.units.name = "c.";
+ ing.groupID = 0; ing.group = "Dry Ingredients";
+ recipe.ingList.append( ing );
+
+ Ingredient ing2;
+ ing2.name = "brown sugar";
+ ing2.amount = 1;
+ ing2.amount_offset = 0;
+ ing2.units.name = "c.";
+ ing2.groupID = 0; ing2.group = "Dry Ingredients";
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing3;
+ ing3.name = "all-purpose flour";
+ ing3.amount = 2;
+ ing3.units.plural = "c.";
+ ing3.groupID = 0; ing3.group = "Dry Ingredients";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "baking soda";
+ ing4.amount = 1;
+ ing4.amount_offset = 0;
+ ing4.units.name = "tsp.";
+ ing4.groupID = 0; ing4.group = "Dry Ingredients";
+ recipe.ingList.append( ing4 );
+
+ Ingredient ing8;
+ ing8.name = "shortening";
+ ing8.amount = 1;
+ ing8.amount_offset = 0;
+ ing8.units.name = "c.";
+ ing8.prepMethodList.append( Element("softened") );
+ ing8.prepMethodList.append( Element("at room temperature") );
+ ing8.groupID = 1; ing8.group = "Fat & Liquids";
+ recipe.ingList.append( ing8 );
+
+ Ingredient ing6;
+ ing6.name = "peanut butter";
+ ing6.amount = 1;
+ ing6.amount_offset = 0;
+ ing6.units.name = "c.";
+ ing6.groupID = 1; ing6.group = "Fat & Liquids";
+ recipe.ingList.append( ing6 );
+
+ Ingredient ing5;
+ ing5.name = "eggs";
+ ing5.amount = 2;
+ ing5.amount_offset = 0;
+ ing5.units.plural = "";
+ ing5.groupID = 1; ing5.group = "Fat & Liquids";
+ recipe.ingList.append( ing5 );
+
+ Ingredient ing7;
+ ing7.name = "vanilla extract";
+ ing7.amount = 1;
+ ing7.amount_offset = 0;
+ ing7.units.name = "tsp.";
+ ing7.groupID = 1; ing7.group = "Fat & Liquids";
+ recipe.ingList.append( ing7 );
+
+ Ingredient ing9;
+ ing9.name = "a";
+ ing9.amount = 1;
+ ing9.amount_offset = 0;
+ ing9.units.name = "cup";
+ IngredientData ing9_1;
+ ing9_1.name = "b";
+ ing9_1.amount = 2;
+ ing9_1.amount_offset = 0;
+ ing9_1.units.plural = "cups";
+ IngredientData ing9_2;
+ ing9_2.name = "c";
+ ing9_2.amount = 3;
+ ing9_2.amount_offset = 0;
+ ing9_2.units.plural = "cups";
+ ing9.substitutes.append(ing9_1);
+ ing9.substitutes.append(ing9_2);
+ recipe.ingList.append( ing9 );
+
+ check( importer, recipe );
+
+ RecipeList recipeList;
+ recipeList.append(recipe);
+ recipeList.append(recipe);
+
+ printf("Creating RecipeMLExporter.\n");
+ RecipeMLExporter exporter("not needed",".mmf");
+ check( exporter, recipeList );
+ printf("Successfully exported recipes to test.txt.\n");
+
+ printf("Creating RecipeMLImporter to test exported recipes.\n");
+ RecipeMLImporter importer2;
+
+ printf("Parsing test.txt.\n");
+ QStringList files2; files2 << "test.txt";
+ importer2.parseFiles(files2);
+ QFile::remove("test.txt");
+ check( importer2, recipe );
+ printf("Recipe export successful.\n");
+
+ printf("*** RecipeML importer and exporter passed the tests :-) ***\n");
+
+ printf("Done.\n");
+}
diff --git a/krecipes/src/tests/recipemltest.txt b/krecipes/src/tests/recipemltest.txt
new file mode 100644
index 0000000..330f757
--- /dev/null
+++ b/krecipes/src/tests/recipemltest.txt
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE recipeml PUBLIC "-//FormatData//DTD RecipeML 0.5//EN" "http://www.formatdata.com/recipeml/recipeml.dtd"><recipeml version="0.5" generator="Krecipes vSVN_PRE-0.9">
+<recipe>
+ <head>
+ <title>Cookies_Test</title>
+ <source>
+ <srcitem>Colleen Beamer</srcitem>
+ <srcitem>Mona Beamer</srcitem>
+ </source>
+ <categories>
+ <cat>Snacks</cat>
+ <cat>Cookies &amp; Squares</cat>
+ </categories>
+ <yield>
+ <range>
+ <q1>2</q1>
+ <q2>3</q2>
+ </range>
+ <unit>dozen</unit>
+ </yield>
+ <preptime type="Total" >
+ <time>
+ <qty>30</qty>
+ <timeunit>minutes</timeunit>
+ </time>
+ </preptime>
+ </head>
+ <ingredients>
+ <ing-div>
+ <title>Dry Ingredients</title>
+ <ing>
+ <amt>
+ <qty>
+ <range>
+ <q1>0.75</q1>
+ <q2>1</q2>
+ </range>
+ </qty>
+ <unit>c.</unit>
+ </amt>
+ <item>granulated sugar</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>brown sugar</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>2</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>all-purpose flour</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>tsp.</unit>
+ </amt>
+ <item>baking soda</item>
+ </ing>
+ </ing-div>
+ <ing-div>
+ <title>Fat &amp; Liquids</title>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>shortening</item>
+ <prep>softened,at room temperature</prep>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>peanut butter</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>2</qty>
+ <unit></unit>
+ </amt>
+ <item>eggs</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>tsp.</unit>
+ </amt>
+ <item>vanilla extract</item>
+ </ing>
+ </ing-div>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>cup</unit>
+ </amt>
+ <item>a</item>
+ <prep></prep>
+ <alt-ing>
+ <amt>
+ <qty>2</qty>
+ <unit>cups</unit>
+ </amt>
+ <item>b</item>
+ <prep></prep>
+ </alt-ing>
+ <alt-ing>
+ <amt>
+ <qty>3</qty>
+ <unit>cups</unit>
+ </amt>
+ <item>c</item>
+ <prep></prep>
+ </alt-ing>
+ </ing>
+ </ingredients>
+ <directions>
+ <step>Drop by spoonful on greased cookie sheet. Bake in moderate oven.</step>
+ </directions>
+</recipe>
+<recipe>
+ <head>
+ <title>Cookies_Test</title>
+ <source>
+ <srcitem>Colleen Beamer</srcitem>
+ <srcitem>Mona Beamer</srcitem>
+ </source>
+ <categories>
+ <cat>Snacks</cat>
+ <cat>Cookies &amp; Squares</cat>
+ </categories>
+ <yield>
+ <range>
+ <q1>2</q1>
+ <q2>3</q2>
+ </range>
+ <unit>dozen</unit>
+ </yield>
+ <preptime type="Total" >
+ <time>
+ <qty>30</qty>
+ <timeunit>minutes</timeunit>
+ </time>
+ </preptime>
+ </head>
+ <ingredients>
+ <ing-div>
+ <title>Dry Ingredients</title>
+ <ing>
+ <amt>
+ <qty>
+ <range>
+ <q1>0.75</q1>
+ <q2>1</q2>
+ </range>
+ </qty>
+ <unit>c.</unit>
+ </amt>
+ <item>granulated sugar</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>brown sugar</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>2</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>all-purpose flour</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>tsp.</unit>
+ </amt>
+ <item>baking soda</item>
+ </ing>
+ </ing-div>
+ <ing-div>
+ <title>Fat &amp; Liquids</title>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>shortening</item>
+ <prep>softened,at room temperature</prep>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>c.</unit>
+ </amt>
+ <item>peanut butter</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>2</qty>
+ <unit></unit>
+ </amt>
+ <item>eggs</item>
+ </ing>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>tsp.</unit>
+ </amt>
+ <item>vanilla extract</item>
+ </ing>
+ </ing-div>
+ <ing>
+ <amt>
+ <qty>1</qty>
+ <unit>cup</unit>
+ </amt>
+ <item>a</item>
+ <prep></prep>
+ <alt-ing>
+ <amt>
+ <qty>2</qty>
+ <unit>cups</unit>
+ </amt>
+ <item>b</item>
+ <prep></prep>
+ </alt-ing>
+ <alt-ing>
+ <amt>
+ <qty>3</qty>
+ <unit>cups</unit>
+ </amt>
+ <item>c</item>
+ <prep></prep>
+ </alt-ing>
+ </ing>
+ </ingredients>
+ <directions>
+ <step>Drop by spoonful on greased cookie sheet. Bake in moderate oven.</step>
+ </directions>
+</recipe>
+</recipeml> \ No newline at end of file
diff --git a/krecipes/src/tests/rezkonvtest.cpp b/krecipes/src/tests/rezkonvtest.cpp
new file mode 100644
index 0000000..cd1e983
--- /dev/null
+++ b/krecipes/src/tests/rezkonvtest.cpp
@@ -0,0 +1,155 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include <kapplication.h>
+
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::endl;
+
+#include "rezkonvimporter.h"
+#include "rezkonvexporter.h"
+#include "importertest.h"
+#include "exportertest.h"
+
+int
+main(int argc, char *argv[])
+{
+ KApplication a(argc, argv, "rezkonvtest");
+
+ printf("Creating RezkonvImporter.\n");
+ RezkonvImporter importer;
+
+ printf("Parsing rezkonvtest.txt.\n");
+ QStringList files; files << "rezkonvtest.txt";
+ importer.parseFiles(files);
+
+ Recipe recipe;
+ recipe.title = "Cookies_Test";
+ recipe.yield.amount = 2;
+ recipe.yield.amount_offset = 1;
+ recipe.yield.type = "dozen";
+ recipe.categoryList.append( Element("Snacks") );
+ recipe.categoryList.append( Element("Cookies & Squares") );
+ recipe.instructions =
+ "\n\nDrop by spoonful on greased cookie sheet. Bake in moderate oven.";
+
+ recipe.authorList.append( Element("Mona Beamer") );
+ recipe.authorList.append( Element("Colleen Beamer") );
+
+ Ingredient ing9;
+ ing9.name = "a";
+ ing9.amount = 1;
+ ing9.amount_offset = 0;
+ ing9.units.name = "cup";
+ IngredientData ing9_1;
+ ing9_1.name = "b";
+ ing9_1.amount = 2;
+ ing9_1.amount_offset = 0;
+ ing9_1.units.plural = "cups";
+ IngredientData ing9_2;
+ ing9_2.name = "c";
+ ing9_2.amount = 3;
+ ing9_2.amount_offset = 0;
+ ing9_2.units.plural = "cups";
+ ing9.substitutes.append(ing9_1);
+ ing9.substitutes.append(ing9_2);
+ recipe.ingList.append( ing9 );
+
+ Ingredient ing;
+ ing.name = "granulated sugar";
+ ing.amount = 0.75;
+ ing.amount_offset = 0.25;
+ ing.units.name = "c.";
+ ing.groupID = 0; ing.group = "Dry Ingredients";
+ recipe.ingList.append( ing );
+
+ Ingredient ing2;
+ ing2.name = "brown sugar";
+ ing2.amount = 1;
+ ing2.amount_offset = 0;
+ ing2.units.name = "c.";
+ ing2.groupID = 0; ing2.group = "Dry Ingredients";
+ recipe.ingList.append( ing2 );
+
+ Ingredient ing3;
+ ing3.name = "all-purpose flour";
+ ing3.amount = 2;
+ ing3.units.plural = "c.";
+ ing3.groupID = 0; ing3.group = "Dry Ingredients";
+ recipe.ingList.append( ing3 );
+
+ Ingredient ing4;
+ ing4.name = "baking soda";
+ ing4.amount = 1;
+ ing4.amount_offset = 0;
+ ing4.units.name = "tsp.";
+ ing4.groupID = 0; ing4.group = "Dry Ingredients";
+ recipe.ingList.append( ing4 );
+
+ Ingredient ing8;
+ ing8.name = "shortening";
+ ing8.amount = 1;
+ ing8.amount_offset = 0;
+ ing8.units.name = "c.";
+ ing8.prepMethodList.append( Element("softened") );
+ ing8.prepMethodList.append( Element("at room temperature") );
+ ing8.groupID = 1; ing8.group = "Fat & Liquids";
+ recipe.ingList.append( ing8 );
+
+ Ingredient ing6;
+ ing6.name = "peanut butter";
+ ing6.amount = 1;
+ ing6.amount_offset = 0;
+ ing6.units.name = "c.";
+ ing6.groupID = 1; ing6.group = "Fat & Liquids";
+ recipe.ingList.append( ing6 );
+
+ Ingredient ing5;
+ ing5.name = "eggs";
+ ing5.amount = 2;
+ ing5.amount_offset = 0;
+ ing5.units.plural = "";
+ ing5.groupID = 1; ing5.group = "Fat & Liquids";
+ recipe.ingList.append( ing5 );
+
+ Ingredient ing7;
+ ing7.name = "vanilla extract";
+ ing7.amount = 1;
+ ing7.amount_offset = 0;
+ ing7.units.name = "tsp.";
+ ing7.groupID = 1; ing7.group = "Fat & Liquids";
+ recipe.ingList.append( ing7 );
+
+ check( importer, recipe );
+
+ RecipeList recipeList;
+ recipeList.append(recipe);
+ recipeList.append(recipe);
+
+ printf("Creating RezkonvExporter.\n");
+ RezkonvExporter exporter("not needed",".rk");
+ check( exporter, recipeList );
+ printf("Successfully exported recipes to test.txt.\n");
+
+ printf("Creating RezkonvImporter to test exported recipes.\n");
+ RezkonvImporter importer2;
+
+ printf("Parsing test.txt.\n");
+ QStringList files2; files2 << "test.txt";
+ importer2.parseFiles(files2);
+ QFile::remove("test.txt");
+ check( importer2, recipe );
+ printf("Recipe export successful.\n");
+
+ printf("Done.\n");
+}
diff --git a/krecipes/src/tests/rezkonvtest.txt b/krecipes/src/tests/rezkonvtest.txt
new file mode 100644
index 0000000..38e4ac2
--- /dev/null
+++ b/krecipes/src/tests/rezkonvtest.txt
@@ -0,0 +1,55 @@
+========== REZKONV-Rezept - RezkonvSuite v0.97.1
+
+ Titel: Cookies_Test
+Kategorien: Snacks, Cookies & Squares
+ Menge: 2-3 dozen
+
+ 1 cup a, or
+ 2 cups b, or
+ 3 cups c
+========================== Dry Ingredients ==========================
+ 3/4-1 c. granulated sugar
+ 1 c. brown sugar
+ 2 c. all-purpose flour
+ 1 tsp. baking soda
+
+=========================== Fat & Liquids ===========================
+ 1 c. shortening, softened,at room temperature
+ 1 c. peanut butter
+ 2 eggs
+ 1 tsp. vanilla extract
+
+============================== QUELLE ==============================
+ Mona Beamer
+ -- Colleen Beamer
+
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+=====
+
+========== REZKONV-Rezept - RezkonvSuite v0.97.1
+
+ Titel: Cookies_Test
+Kategorien: Snacks, Cookies & Squares
+ Menge: 2-3 dozen
+
+ 1 cup a, or
+ 2 cups b, or
+ 3 cups c
+========================== Dry Ingredients ==========================
+ 3/4-1 c. granulated sugar
+ 1 c. brown sugar
+ 2 c. all-purpose flour
+ 1 tsp. baking soda
+
+=========================== Fat & Liquids ===========================
+ 1 c. shortening, softened,at room temperature
+ 1 c. peanut butter
+ 2 eggs
+ 1 tsp. vanilla extract
+
+============================== QUELLE ==============================
+ Mona Beamer
+ -- Colleen Beamer
+
+Drop by spoonful on greased cookie sheet. Bake in moderate oven.
+=====
diff --git a/krecipes/src/tests/test_photo.jpg b/krecipes/src/tests/test_photo.jpg
new file mode 100644
index 0000000..3ebe035
--- /dev/null
+++ b/krecipes/src/tests/test_photo.jpg
Binary files differ
diff --git a/krecipes/src/widgets/Makefile.am b/krecipes/src/widgets/Makefile.am
new file mode 100644
index 0000000..9f61705
--- /dev/null
+++ b/krecipes/src/widgets/Makefile.am
@@ -0,0 +1,28 @@
+## Makefile.am for krecipes
+
+# this is the program that gets installed. it's name is used for all
+# of the other Makefile.am variables
+
+# set the include path for X, qt and KDE
+INCLUDES = -I$(srcdir)/.. $(all_includes)
+
+noinst_LTLIBRARIES=libkrecipeswidgets.la
+
+libkrecipeswidgets_la_SOURCES= \
+ krelistview.cpp kremenu.cpp \
+ paneldeco.cpp ingredientlistview.cpp unitlistview.cpp \
+ propertylistview.cpp prepmethodlistview.cpp categorylistview.cpp \
+ authorlistview.cpp recipelistview.cpp categorycombobox.cpp \
+ kretextedit.cpp dblistviewbase.cpp \
+ conversiontable.cpp fractioninput.cpp ingredientcombobox.cpp \
+ headercombobox.cpp prepmethodcombobox.cpp \
+ inglistviewitem.cpp kdateedit.cpp kdatepickerpopup.cpp \
+ headerlistview.cpp ratingwidget.cpp kwidgetlistbox.cpp \
+ ratingdisplaywidget.ui criteriacombobox.cpp ingredientinputwidget.cpp \
+ unitcombobox.cpp amountunitinput.cpp weightinput.cpp
+
+
+libkrecipeswidgets_la_METASOURCES=AUTO
+
+#the library search path.
+libkrecipeswidgets_la_LDFLAGS = $(KDE_RPATH) $(all_libraries)
diff --git a/krecipes/src/widgets/amountunitinput.cpp b/krecipes/src/widgets/amountunitinput.cpp
new file mode 100644
index 0000000..8432544
--- /dev/null
+++ b/krecipes/src/widgets/amountunitinput.cpp
@@ -0,0 +1,68 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "amountunitinput.h"
+
+#include <qheader.h>
+#include <qlistview.h>
+
+#include "fractioninput.h"
+#include "unitcombobox.h"
+#include "backends/recipedb.h"
+#include "datablocks/mixednumber.h"
+
+AmountUnitInput::AmountUnitInput( QWidget *parent, RecipeDB *database, Unit::Type type, MixedNumber::Format format ) : QHBox(parent),
+ m_item(0), m_database(database)
+{
+ amountInput = new FractionInput(this,format);
+ unitBox = new UnitComboBox(this,database,type);
+ unitBox->reload();
+
+ connect( amountInput, SIGNAL(valueChanged(const MixedNumber &)), SLOT(emitValueChanged()) );
+ connect( unitBox, SIGNAL(activated(int)), SLOT(emitValueChanged()) );
+ connect( amountInput, SIGNAL(returnPressed()), SIGNAL(doneEditing()) );
+}
+
+void AmountUnitInput::emitValueChanged()
+{
+ emit valueChanged( amount(), unit() );
+}
+
+void AmountUnitInput::setAmount( const MixedNumber &amount )
+{
+ amountInput->disconnect( this );
+ if ( amount.toDouble() < 0 )
+ amountInput->clear();
+ else
+ amountInput->setValue( amount, 0 );
+ connect( amountInput, SIGNAL(valueChanged(const MixedNumber &)), SLOT(emitValueChanged()) );
+}
+
+void AmountUnitInput::setUnit( const Unit &unit )
+{
+ if ( unit.id == -1 )
+ unitBox->setCurrentItem(0);
+ else
+ unitBox->setSelected( unit.id );
+
+}
+
+MixedNumber AmountUnitInput::amount() const
+{
+ return amountInput->value();
+}
+
+Unit AmountUnitInput::unit() const
+{
+ //TODO Potential for optimization here... avoid the database call
+ return m_database->unitName( unitBox->id( unitBox->currentItem() ) );
+}
+
+#include "amountunitinput.moc"
diff --git a/krecipes/src/widgets/amountunitinput.h b/krecipes/src/widgets/amountunitinput.h
new file mode 100644
index 0000000..a7a9ab5
--- /dev/null
+++ b/krecipes/src/widgets/amountunitinput.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef AMOUNTUNITINPUT_H
+#define AMOUNTUNITINPUT_H
+
+#include <qhbox.h>
+
+#include "datablocks/unit.h"
+#include "datablocks/mixednumber.h"
+
+class QListViewItem;
+
+class RecipeDB;
+class FractionInput;
+class UnitComboBox;
+
+class AmountUnitInput : public QHBox
+{
+Q_OBJECT
+
+public:
+ AmountUnitInput( QWidget *parent, RecipeDB *database, Unit::Type type = Unit::All, MixedNumber::Format f = MixedNumber::MixedNumberFormat );
+
+ void setAmount( const MixedNumber &amount );
+ void setUnit( const Unit &unit );
+
+ MixedNumber amount() const;
+ Unit unit() const;
+ QListViewItem *item() const { return m_item; }
+ void setItem( QListViewItem *it ){ m_item = it; }
+
+ void insertIntoListview( QListViewItem *it, int col );
+
+public slots:
+ void emitValueChanged();
+
+signals:
+ void valueChanged( const MixedNumber &, const Unit &unit );
+ void doneEditing();
+
+private:
+ FractionInput *amountInput;
+ UnitComboBox *unitBox;
+
+ QListViewItem *m_item;
+
+ RecipeDB *m_database;
+};
+#endif
diff --git a/krecipes/src/widgets/authorlistview.cpp b/krecipes/src/widgets/authorlistview.cpp
new file mode 100644
index 0000000..cfc4b70
--- /dev/null
+++ b/krecipes/src/widgets/authorlistview.cpp
@@ -0,0 +1,275 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "authorlistview.h"
+
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include "backends/recipedb.h"
+#include "dialogs/createelementdialog.h"
+#include "dialogs/dependanciesdialog.h"
+
+AuthorListView::AuthorListView( QWidget *parent, RecipeDB *db ) : DBListViewBase( parent, db, db->authorCount() )
+{
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+}
+
+void AuthorListView::init()
+{
+ connect( database, SIGNAL( authorCreated( const Element & ) ), SLOT( checkCreateAuthor( const Element & ) ) );
+ connect( database, SIGNAL( authorRemoved( int ) ), SLOT( removeAuthor( int ) ) );
+}
+
+void AuthorListView::load( int limit, int offset )
+{
+ ElementList authorList;
+ database->loadAuthors( &authorList, limit, offset );
+
+ setTotalItems(authorList.count());
+
+ for ( ElementList::const_iterator ing_it = authorList.begin(); ing_it != authorList.end(); ++ing_it )
+ createAuthor( *ing_it );
+}
+
+void AuthorListView::checkCreateAuthor( const Element &el )
+{
+ if ( handleElement(el.name) ) { //only create this author if the base class okays it
+ createAuthor(el);
+ }
+}
+
+
+StdAuthorListView::StdAuthorListView( QWidget *parent, RecipeDB *db, bool editable ) : AuthorListView( parent, db )
+{
+ addColumn( i18n( "Author" ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+
+ KIconLoader *il = new KIconLoader;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il->loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_N );
+ kpop->insertItem( il->loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->polish();
+
+ delete il;
+
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem* ) ), this, SLOT( modAuthor( QListViewItem* ) ) );
+ connect( this, SIGNAL( itemRenamed( QListViewItem* ) ), this, SLOT( saveAuthor( QListViewItem* ) ) );
+ }
+}
+
+void StdAuthorListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdAuthorListView::createNew()
+{
+ CreateElementDialog * elementDialog = new CreateElementDialog( this, i18n( "New Author" ) );
+
+ if ( elementDialog->exec() == QDialog::Accepted ) {
+ QString result = elementDialog->newElementName();
+
+ //check bounds first
+ if ( checkBounds( result ) )
+ database->createNewAuthor( result ); // Create the new author in the database
+ }
+}
+
+void StdAuthorListView::remove
+ ()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item ) {
+ int id = item->text( 1 ).toInt();
+
+ ElementList recipeDependancies;
+ database->findUseOfAuthorInRecipes( &recipeDependancies, id );
+
+ if ( recipeDependancies.isEmpty() ) {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to delete this author?" ) ) ) {
+ case KMessageBox::Continue:
+ database->removeAuthor( id );
+ break;
+ }
+ return;
+ }
+ else { // need warning!
+ ListInfo info;
+ info.list = recipeDependancies;
+ info.name = i18n("Recipes");
+
+ DependanciesDialog warnDialog( this, info, false );
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removeAuthor( id );
+ }
+ }
+}
+
+void StdAuthorListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item )
+ AuthorListView::rename( item, 0 );
+}
+
+void StdAuthorListView::createAuthor( const Element &author )
+{
+ createElement(new QListViewItem( this, author.name, QString::number( author.id ) ));
+}
+
+void StdAuthorListView::removeAuthor( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 1 );
+ removeElement(item);
+}
+
+void StdAuthorListView::modAuthor( QListViewItem* i )
+{
+ if ( i )
+ AuthorListView::rename( i, 0 );
+}
+
+void StdAuthorListView::saveAuthor( QListViewItem* i )
+{
+ if ( !checkBounds( i->text( 0 ) ) ) {
+ reload(ForceReload); //reset the changed text
+ return ;
+ }
+
+ int existing_id = database->findExistingAuthorByName( i->text( 0 ) );
+ int author_id = i->text( 1 ).toInt();
+ if ( existing_id != -1 && existing_id != author_id ) //category already exists with this label... merge the two
+ {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This author already exists. Continuing will merge these two authors into one. Are you sure?" ) ) )
+ {
+ case KMessageBox::Continue: {
+ database->mergeAuthors( existing_id, author_id );
+ break;
+ }
+ default:
+ reload(ForceReload);
+ break;
+ }
+ }
+ else {
+ database->modAuthor( ( i->text( 1 ) ).toInt(), i->text( 0 ) );
+ }
+}
+
+bool StdAuthorListView::checkBounds( const QString &name )
+{
+ if ( name.length() > uint(database->maxAuthorNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Author name cannot be longer than %1 characters." ) ).arg( database->maxAuthorNameLength() ) );
+ return false;
+ }
+
+ return true;
+}
+
+
+AuthorCheckListItem::AuthorCheckListItem( AuthorCheckListView* qlv, const Element &author ) : QCheckListItem( qlv, QString::null, QCheckListItem::CheckBox ),
+ authorStored(author),
+ m_listview(qlv)
+{
+}
+
+AuthorCheckListItem::AuthorCheckListItem( AuthorCheckListView* qlv, QListViewItem *after, const Element &author ) : QCheckListItem( qlv, after, QString::null, QCheckListItem::CheckBox ),
+ authorStored(author),
+ m_listview(qlv)
+{
+}
+
+Element AuthorCheckListItem::author() const
+{
+ return authorStored;
+}
+
+QString AuthorCheckListItem::text( int column ) const
+{
+ switch ( column ) {
+ case 0:
+ return ( authorStored.name );
+ case 1:
+ return ( QString::number( authorStored.id ) );
+ default:
+ return QString::null;
+ }
+}
+
+void AuthorCheckListItem::stateChange( bool on )
+{
+ m_listview->stateChange(this,on);
+}
+
+
+AuthorCheckListView::AuthorCheckListView( QWidget *parent, RecipeDB *db ) : AuthorListView( parent, db )
+{
+ addColumn( i18n( "Author" ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+}
+
+void AuthorCheckListView::createAuthor( const Element &author )
+{
+ createElement(new AuthorCheckListItem( this, author ));
+}
+
+void AuthorCheckListView::removeAuthor( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 1 );
+ removeElement(item);
+}
+
+void AuthorCheckListView::load( int limit, int offset )
+{
+ AuthorListView::load(limit,offset);
+
+ for ( QValueList<Element>::const_iterator author_it = m_selections.begin(); author_it != m_selections.end(); ++author_it ) {
+ QCheckListItem * item = ( QCheckListItem* ) findItem( QString::number( (*author_it).id ), 1 );
+ if ( item ) {
+ item->setOn(true);
+ }
+ }
+}
+
+void AuthorCheckListView::stateChange(AuthorCheckListItem *it,bool on)
+{
+ if ( !reloading() ) {
+ if ( on )
+ m_selections.append(it->author());
+ else
+ m_selections.remove(it->author());
+ }
+}
+
+#include "authorlistview.moc"
diff --git a/krecipes/src/widgets/authorlistview.h b/krecipes/src/widgets/authorlistview.h
new file mode 100644
index 0000000..2b6faa8
--- /dev/null
+++ b/krecipes/src/widgets/authorlistview.h
@@ -0,0 +1,107 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef AUTHORLISTVIEW_H
+#define AUTHORLISTVIEW_H
+
+#include "dblistviewbase.h"
+#include "datablocks/element.h"
+
+class RecipeDB;
+class KPopupMenu;
+
+class AuthorCheckListView;
+
+class AuthorCheckListItem: public QCheckListItem
+{
+public:
+ AuthorCheckListItem( AuthorCheckListView* qlv, const Element &author );
+ AuthorCheckListItem( AuthorCheckListView* qlv, QListViewItem *after, const Element &author );
+
+ Element author() const;
+
+ virtual QString text( int column ) const;
+
+protected:
+ virtual void stateChange( bool on );
+
+private:
+ Element authorStored;
+ AuthorCheckListView *m_listview;
+};
+
+
+class AuthorListView : public DBListViewBase
+{
+ Q_OBJECT
+
+public:
+ AuthorListView( QWidget *parent, RecipeDB *db );
+
+protected slots:
+ void checkCreateAuthor( const Element &el );
+ virtual void createAuthor( const Element & ) = 0;
+ virtual void removeAuthor( int ) = 0;
+ virtual void load( int curr_limit, int curr_offset );
+
+protected:
+ virtual void init();
+};
+
+class StdAuthorListView : public AuthorListView
+{
+ Q_OBJECT
+
+public:
+ StdAuthorListView( QWidget *parent, RecipeDB *db, bool editable = false );
+
+protected:
+ virtual void createAuthor( const Element & );
+ virtual void removeAuthor( int );
+
+private slots:
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void createNew();
+ void remove
+ ();
+ void rename();
+
+ void modAuthor( QListViewItem* i );
+ void saveAuthor( QListViewItem* i );
+
+private:
+ bool checkBounds( const QString &name );
+
+ KPopupMenu *kpop;
+};
+
+
+class AuthorCheckListView : public AuthorListView
+{
+public:
+ AuthorCheckListView( QWidget *parent, RecipeDB *db );
+
+ virtual void stateChange(AuthorCheckListItem *,bool);
+
+ QValueList<Element> selections() const{ return m_selections; }
+
+protected:
+ virtual void createAuthor( const Element &ing );
+ virtual void removeAuthor( int );
+
+ virtual void load( int limit, int offset );
+
+private:
+ QValueList<Element> m_selections;
+};
+
+#endif //AUTHORLISTVIEW_H
diff --git a/krecipes/src/widgets/categorycombobox.cpp b/krecipes/src/widgets/categorycombobox.cpp
new file mode 100644
index 0000000..7ff75b4
--- /dev/null
+++ b/krecipes/src/widgets/categorycombobox.cpp
@@ -0,0 +1,182 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "categorycombobox.h"
+
+#include <qlistbox.h>
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kglobal.h>
+
+#include "backends/recipedb.h"
+#include "backends/progressinterface.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/categorytree.h"
+
+CategoryComboBox::CategoryComboBox( QWidget *parent, RecipeDB *db ) : KComboBox( parent ),
+ database( db ),
+ m_offset(0)
+{
+ connect( database, SIGNAL( categoryCreated( const Element &, int ) ), SLOT( createCategory( const Element &, int ) ) );
+ connect( database, SIGNAL( categoryRemoved( int ) ), SLOT( removeCategory( int ) ) );
+ connect( database, SIGNAL( categoryModified( const Element & ) ), SLOT( modifyCategory( const Element & ) ) );
+ connect( database, SIGNAL( categoriesMerged( int, int ) ), SLOT( mergeCategories( int, int ) ) );
+
+ // Insert default "All Categories" (row 0, which will be translated to -1 as category in the filtering process)
+ // the rest of the items are loaded when needed in order to significantly speed up startup
+ insertItem( i18n( "All Categories" ) );
+}
+
+void CategoryComboBox::popup()
+{
+ if ( count() == 1 )
+ reload();
+ KComboBox::popup();
+}
+
+void CategoryComboBox::reload()
+{
+ QString remember_cat_filter = currentText();
+
+ KConfig * config = KGlobal::config();config->setGroup( "Performance" );
+ int limit = config->readNumEntry( "CategoryLimit", -1 );
+
+ //ProgressInterface pi(this);
+ //pi.listenOn(database);
+
+ CategoryTree categoryList;
+ database->loadCategories( &categoryList, limit, m_offset, -1 );
+
+ clear();
+ categoryComboRows.clear();
+
+ // Insert default "All Categories" (row 0, which will be translated to -1 as category in the filtering process)
+ insertItem( i18n( "All Categories" ) );
+
+ //Now load the categories
+ int row = 1;
+ loadCategories(&categoryList,row);
+
+ if ( listBox() ->findItem( remember_cat_filter, Qt::ExactMatch ) ) {
+ setCurrentText( remember_cat_filter );
+ }
+}
+
+void CategoryComboBox::loadCategories( CategoryTree *categoryTree, int &row )
+{
+ for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ insertItem( child_it->category.name );
+ categoryComboRows.insert( row, child_it->category.id ); // store category id's in the combobox position to obtain the category id later
+ row++;
+ loadCategories( child_it, row );
+ }
+}
+
+void CategoryComboBox::loadNextGroup()
+{
+ KConfig * config = KGlobal::config();config->setGroup( "Performance" );
+ int limit = config->readNumEntry( "CategoryLimit", -1 );
+
+ m_offset += limit;
+
+ reload();
+}
+
+void CategoryComboBox::loadPrevGroup()
+{
+ KConfig * config = KGlobal::config();config->setGroup( "Performance" );
+ int limit = config->readNumEntry( "CategoryLimit", -1 );
+
+ m_offset -= limit;
+
+ reload();
+}
+
+int CategoryComboBox::id( int row )
+{
+ if ( row )
+ return categoryComboRows[ row ];
+ else
+ return -1; // No category filtering
+}
+
+void CategoryComboBox::createCategory( const Element &element, int /*parent_id*/ )
+{
+ int row = findInsertionPoint( element.name );
+
+ insertItem( element.name, row );
+
+ //now update the map by pushing everything after this item down
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) {
+ if ( it.key() >= row ) {
+ new_map.insert( it.key() + 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ categoryComboRows = new_map;
+ categoryComboRows.insert( row, element.id );
+}
+
+void CategoryComboBox::removeCategory( int id )
+{
+ int row = -1;
+ for ( QMap<int, int>::iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) {
+ if ( it.data() == id ) {
+ row = it.key();
+ removeItem( row );
+ categoryComboRows.remove( it );
+ break;
+ }
+ }
+
+ if ( row == -1 )
+ return ;
+
+ //now update the map by pushing everything after this item up
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) {
+ if ( it.key() > row ) {
+ new_map.insert( it.key() - 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ categoryComboRows = new_map;
+}
+
+void CategoryComboBox::modifyCategory( const Element &element )
+{
+ for ( QMap<int, int>::const_iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) {
+ if ( it.data() == element.id )
+ changeItem( element.name, it.key() );
+ }
+}
+
+void CategoryComboBox::mergeCategories( int /*to_id*/, int from_id )
+{
+ removeCategory( from_id );
+}
+
+int CategoryComboBox::findInsertionPoint( const QString &name )
+{
+ for ( int i = 1; i < count(); i++ ) {
+ if ( QString::localeAwareCompare( name, text( i ) ) < 0 )
+ return i;
+ }
+
+ return count();
+}
+
+#include "categorycombobox.moc"
diff --git a/krecipes/src/widgets/categorycombobox.h b/krecipes/src/widgets/categorycombobox.h
new file mode 100644
index 0000000..5bb8186
--- /dev/null
+++ b/krecipes/src/widgets/categorycombobox.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CATEGORYCOMBOBOX_H
+#define CATEGORYCOMBOBOX_H
+
+#include <kcombobox.h>
+
+#include <qmap.h>
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+class CategoryTree;
+
+class CategoryComboBox : public KComboBox
+{
+ Q_OBJECT
+
+public:
+ CategoryComboBox( QWidget *parent, RecipeDB *db );
+
+ void reload();
+ int id( int row );
+
+public slots:
+ void loadNextGroup();
+ void loadPrevGroup();
+
+protected:
+ virtual void popup();
+
+private slots:
+ void createCategory( const Element &element, int /*parent_id*/ );
+ void removeCategory( int id );
+ void modifyCategory( const Element &element );
+ void mergeCategories( int /*to_id*/, int from_id );
+
+ int findInsertionPoint( const QString &name );
+
+private:
+ void loadCategories( CategoryTree *categoryList, int &row );
+
+ RecipeDB *database;
+ QMap<int, int> categoryComboRows; // Contains the category id for every given row in the category combobox
+ int m_offset;
+};
+
+#endif //CATEGORYCOMBOBOX_H
+
diff --git a/krecipes/src/widgets/categorylistview.cpp b/krecipes/src/widgets/categorylistview.cpp
new file mode 100644
index 0000000..35186a7
--- /dev/null
+++ b/krecipes/src/widgets/categorylistview.cpp
@@ -0,0 +1,637 @@
+
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "categorylistview.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/categorytree.h"
+#include "dialogs/createcategorydialog.h"
+#include "dialogs/dependanciesdialog.h"
+
+CategoryCheckListItem::CategoryCheckListItem( CategoryCheckListView* klv, const Element &category, bool _exclusive ) : QCheckListItem( klv, QString::null, QCheckListItem::CheckBox ), CategoryItemInfo( category ),
+ locked( false ),
+ exclusive( _exclusive ),
+ m_listview(klv)
+{
+ setOn( false ); // Set unchecked by default
+}
+
+CategoryCheckListItem::CategoryCheckListItem( QListViewItem* it, const Element &category, bool _exclusive ) : QCheckListItem( it, QString::null, QCheckListItem::CheckBox ), CategoryItemInfo( category ),
+ locked( false ),
+ exclusive( _exclusive ),
+ m_listview((CategoryCheckListView*)it->listView())
+{
+ setOn( false ); // Set unchecked by default
+}
+
+CategoryCheckListItem::CategoryCheckListItem( CategoryCheckListView* klv, QListViewItem* it, const Element &category, bool _exclusive ) : QCheckListItem( klv, it, QString::null, QCheckListItem::CheckBox ), CategoryItemInfo( category ),
+ locked( false ),
+ exclusive( _exclusive ),
+ m_listview(klv)
+{
+ setOn( false ); // Set unchecked by default
+}
+
+QString CategoryCheckListItem::text( int column ) const
+{
+ if ( column == 1 )
+ return ( QString::number( ctyStored.id ) );
+ else
+ return ( ctyStored.name );
+}
+
+void CategoryCheckListItem::setText( int column, const QString &text )
+{
+ switch ( column ) {
+ case 0:
+ ctyStored.name = text;
+ break;
+ default:
+ break;
+ }
+}
+
+void CategoryCheckListItem::stateChange( bool on )
+{
+ m_listview->stateChange(this,on);
+
+ if ( locked )
+ return;
+
+ if ( on && exclusive ) {
+ setParentsState( false );
+ setChildrenState( false );
+ }
+}
+
+void CategoryCheckListItem::setChildrenState( bool on )
+{
+ if ( !isPopulated() )
+ return;
+
+ for ( CategoryCheckListItem * cat_it = ( CategoryCheckListItem* ) firstChild(); cat_it; cat_it = ( CategoryCheckListItem* ) cat_it->nextSibling() ) {
+ cat_it->locked = true;
+ cat_it->setOn( on );
+ cat_it->setChildrenState( on );
+ cat_it->locked = false;
+ }
+}
+
+void CategoryCheckListItem::setParentsState( bool on )
+{
+ locked = true;
+
+ CategoryCheckListItem *cat_it;
+ for ( cat_it = ( CategoryCheckListItem* ) parent(); cat_it; cat_it = ( CategoryCheckListItem* ) cat_it->parent() )
+ cat_it->setOn( on );
+
+ locked = false;
+}
+
+
+
+
+CategoryListItem::CategoryListItem( QListView* klv, const Element &category ) : QListViewItem( klv ),
+ CategoryItemInfo(category)
+{}
+
+CategoryListItem::CategoryListItem( QListViewItem* it, const Element &category ) : QListViewItem( it ),
+ CategoryItemInfo(category)
+{}
+
+CategoryListItem::CategoryListItem( QListView* klv, QListViewItem* it, const Element &category ) : QListViewItem( klv, it ),
+ CategoryItemInfo(category)
+{}
+
+QString CategoryListItem::text( int column ) const
+{
+ if ( column == 1 )
+ return ( QString::number( ctyStored.id ) );
+ else
+ return ( ctyStored.name );
+}
+
+void CategoryListItem::setText( int column, const QString &text )
+{
+ if ( column == 0 )
+ ctyStored.name = text;
+}
+
+
+
+CategoryListView::CategoryListView( QWidget *parent, RecipeDB *db ) : DBListViewBase( parent, db, db->categoryTopLevelCount() ),
+ m_item_to_delete(0)
+{
+ //connect( this, SIGNAL( spacePressed(QListViewItem*) ), SLOT( open(QListViewItem*) ) );
+ //connect( this, SIGNAL( returnPressed(QListViewItem*) ), SLOT( open(QListViewItem*) ) );
+ //connect( this, SIGNAL( executed(QListViewItem*) ), SLOT( open(QListViewItem*) ) );
+
+ connect( this, SIGNAL( expanded(QListViewItem*) ), SLOT( open(QListViewItem*) ) );
+
+ setRootIsDecorated( true );
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+}
+
+void CategoryListView::init()
+{
+ connect( database, SIGNAL( categoryCreated( const Element &, int ) ), SLOT( checkCreateCategory( const Element &, int ) ) );
+ connect( database, SIGNAL( categoryRemoved( int ) ), SLOT( removeCategory( int ) ) );
+ connect( database, SIGNAL( categoryModified( const Element & ) ), SLOT( modifyCategory( const Element & ) ) );
+ connect( database, SIGNAL( categoryModified( int, int ) ), SLOT( modifyCategory( int, int ) ) );
+ connect( database, SIGNAL( categoriesMerged( int, int ) ), SLOT( mergeCategories( int, int ) ) );
+}
+
+// (Re)loads the data from the database
+void CategoryListView::load( int limit, int offset )
+{
+ items_map.clear();
+
+ CategoryTree list;
+ CategoryTree *p_list = &list;
+ database->loadCachedCategories( &p_list, limit, offset, -1, false );
+
+ setTotalItems(p_list->count());
+
+ for ( CategoryTree * child_it = p_list->firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ createCategory( child_it->category, -1 );
+ }
+}
+
+void CategoryListView::populate( QListViewItem *item )
+{
+ CategoryItemInfo *cat_item = dynamic_cast<CategoryItemInfo*>(item);
+ if ( !cat_item || cat_item->isPopulated() ) return;
+
+ if ( item->firstChild() && item->firstChild()->rtti() != PSEUDOLISTITEM_RTTI )
+ return;
+
+ delete item->firstChild(); //delete the "pseudo item"
+
+ int id = cat_item->categoryId();
+ cat_item->setPopulated(true);
+
+ CategoryTree categoryTree;
+ database->loadCategories( &categoryTree, -1, 0, id, false );
+
+ for ( CategoryTree * child_it = categoryTree.firstChild(); child_it; child_it = child_it->nextSibling() ) {
+ createCategory( child_it->category, id );
+ }
+}
+
+void CategoryListView::populateAll( QListViewItem *parent )
+{
+ if ( !parent )
+ parent = firstChild();
+
+ for ( QListViewItem *item = parent; item; item = item->nextSibling() ) {
+ populate( item );
+ if ( item->firstChild() )
+ populateAll( item->firstChild() );
+ }
+}
+
+void CategoryListView::open( QListViewItem *item )
+{
+ Q_ASSERT( item );
+ if ( !item->firstChild() || item->firstChild()->rtti() != PSEUDOLISTITEM_RTTI ) return;
+
+ populate(item);
+
+ item->setOpen(true);
+}
+
+void CategoryListView::checkCreateCategory( const Element &el, int parent_id )
+{
+ if ( parent_id != -1 || handleElement(el.name) ) { //only create this category if the base class okays it; allow all non-top-level items
+ createCategory(el,parent_id);
+ }
+}
+
+void CategoryListView::modifyCategory( const Element &category )
+{
+ QListViewItem * item = items_map[ category.id ];
+
+ if ( item )
+ item->setText( 0, category.name );
+}
+
+void CategoryListView::modifyCategory( int id, int parent_id )
+{
+ QMap<int,QListViewItem*>::iterator item_it = items_map.find(id);
+ if ( item_it != items_map.end() ) {
+ QListViewItem *item = *item_it;
+ Q_ASSERT( item );
+
+ removeElement(item,false);
+ if ( !item->parent() )
+ takeItem( item );
+ else
+ item->parent() ->takeItem( item );
+
+ if ( parent_id == -1 ) {
+ insertItem(item);
+ createElement(item);
+ }
+ else {
+ QMap<int,QListViewItem*>::iterator parent_item_it = items_map.find(parent_id);
+ if ( parent_item_it != items_map.end() &&
+ dynamic_cast<CategoryItemInfo*>(*parent_item_it)->isPopulated() ) {
+ (*parent_item_it)->insertItem( item );
+ createElement(item);
+ }
+ else {
+ if ( !(*parent_item_it)->firstChild() )
+ new PseudoListItem( *parent_item_it );
+
+ //removeElement() was already called on this item, so we just delete it
+ //we can't delete it just yet because this function is called by a slot
+ delete m_item_to_delete;
+ m_item_to_delete = item;
+ }
+ }
+ }
+}
+
+void CategoryListView::mergeCategories( int id1, int id2 )
+{
+ QListViewItem * to_item = items_map[ id1 ];
+ QListViewItem *from_item = items_map[ id2 ];
+
+ CategoryItemInfo *info_item = dynamic_cast<CategoryItemInfo*>(to_item);
+
+ if ( to_item && info_item->isPopulated() && from_item ) {
+ //note that this takes care of any recipes that may be children as well
+ QListViewItem *next_sibling;
+ for ( QListViewItem * it = from_item->firstChild(); it; it = next_sibling ) {
+ next_sibling = it->nextSibling(); //get the sibling before we move the item
+
+ removeElement(it,false);
+ from_item->takeItem( it );
+
+ to_item->insertItem( it );
+ createElement(it);
+ }
+ }
+
+ removeCategory( id2 );
+}
+
+
+StdCategoryListView::StdCategoryListView( QWidget *parent, RecipeDB *db, bool editable ) : CategoryListView( parent, db ),
+ clipboard_item( 0 ),
+ clipboard_parent( 0 )
+{
+ addColumn( i18n( "Category" ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+ setDragEnabled( true );
+ setAcceptDrops( true );
+
+ KIconLoader *il = new KIconLoader;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il->loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_C );
+ kpop->insertItem( il->loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->insertSeparator();
+ kpop->insertItem( il->loadIcon( "editcut", KIcon::NoGroup, 16 ), i18n( "Cu&t" ), this, SLOT( cut() ), CTRL + Key_X );
+ kpop->insertItem( il->loadIcon( "editpaste", KIcon::NoGroup, 16 ), i18n( "&Paste" ), this, SLOT( paste() ), CTRL + Key_V );
+ kpop->insertItem( il->loadIcon( "editpaste", KIcon::NoGroup, 16 ), i18n( "Paste as Subcategory" ), this, SLOT( pasteAsSub() ), CTRL + SHIFT + Key_V );
+ kpop->polish();
+
+ delete il;
+
+ connect( kpop, SIGNAL( aboutToShow() ), SLOT( preparePopup() ) );
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), SLOT( modCategory( QListViewItem* ) ) );
+ connect( this, SIGNAL( itemRenamed ( QListViewItem* ) ), SLOT( saveCategory( QListViewItem* ) ) );
+ connect( this, SIGNAL( moved( QListViewItem *, QListViewItem *, QListViewItem * ) ), SLOT( changeCategoryParent( QListViewItem *, QListViewItem *, QListViewItem * ) ) );
+ }
+}
+
+StdCategoryListView::~StdCategoryListView()
+{
+ delete clipboard_item;
+}
+
+void StdCategoryListView::setPixmap( const QPixmap &icon )
+{
+ m_folder_icon = icon;
+}
+
+void StdCategoryListView::preparePopup()
+{
+ //only enable the paste items if clipboard_item isn't null
+ kpop->setItemEnabled( kpop->idAt( 5 ), clipboard_item );
+ kpop->setItemEnabled( kpop->idAt( 6 ), clipboard_item );
+}
+
+void StdCategoryListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdCategoryListView::createNew()
+{
+ ElementList categories;
+ database->loadCategories( &categories );
+ CreateCategoryDialog* categoryDialog = new CreateCategoryDialog( this, categories );
+
+ if ( categoryDialog->exec() == QDialog::Accepted ) {
+ QString result = categoryDialog->newCategoryName();
+ int subcategory = categoryDialog->subcategory();
+
+ //check bounds first
+ if ( checkBounds( result ) )
+ database->createNewCategory( result, subcategory ); // Create the new category in the database
+ }
+ delete categoryDialog;
+}
+
+void StdCategoryListView::remove
+ ()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item ) {
+ int id = item->text( 1 ).toInt();
+
+ ElementList recipeDependancies;
+ database->findUseOfCategoryInRecipes( &recipeDependancies, id );
+
+ if ( recipeDependancies.isEmpty() ) {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to delete this category and all its subcategories?" ) ) ) {
+ case KMessageBox::Continue:
+ database->removeCategory( id );
+ break;
+ }
+ return;
+ }
+ else { // need warning!
+ ListInfo info;
+ info.list = recipeDependancies;
+ info.name = i18n("Recipes");
+ DependanciesDialog warnDialog( this, info, false );
+
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removeCategory( id );
+ }
+ }
+}
+
+void StdCategoryListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item )
+ CategoryListView::rename( item, 0 );
+}
+
+void StdCategoryListView::cut()
+{
+ //restore a never used cut
+ if ( clipboard_item ) {
+ if ( clipboard_parent )
+ clipboard_parent->insertItem( clipboard_item );
+ else
+ insertItem( clipboard_item );
+ clipboard_item = 0;
+ }
+
+ QListViewItem *item = currentItem();
+
+ if ( item ) {
+ clipboard_item = item;
+ clipboard_parent = item->parent();
+
+ if ( item->parent() )
+ item->parent() ->takeItem( item );
+ else
+ takeItem( item );
+ }
+}
+
+void StdCategoryListView::paste()
+{
+ QListViewItem * item = currentItem();
+ if ( item && clipboard_item ) {
+ if ( item->parent() )
+ item->parent() ->insertItem( clipboard_item );
+ else
+ insertItem( clipboard_item );
+
+ database->modCategory( clipboard_item->text( 1 ).toInt(), item->parent() ? item->parent() ->text( 1 ).toInt() : -1 );
+ clipboard_item = 0;
+ }
+}
+
+void StdCategoryListView::pasteAsSub()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item && clipboard_item ) {
+ item->insertItem( clipboard_item );
+ database->modCategory( clipboard_item->text( 1 ).toInt(), item->text( 1 ).toInt() );
+ clipboard_item = 0;
+ }
+}
+
+void StdCategoryListView::changeCategoryParent( QListViewItem *item, QListViewItem * /*afterFirst*/, QListViewItem * /*afterNow*/ )
+{
+ int new_parent_id = -1;
+ if ( QListViewItem * parent = item->parent() )
+ new_parent_id = parent->text( 1 ).toInt();
+
+ int cat_id = item->text( 1 ).toInt();
+
+ disconnect( SIGNAL( moved( QListViewItem *, QListViewItem *, QListViewItem * ) ) );
+ database->modCategory( cat_id, new_parent_id );
+ connect( this, SIGNAL( moved( QListViewItem *, QListViewItem *, QListViewItem * ) ), SLOT( changeCategoryParent( QListViewItem *, QListViewItem *, QListViewItem * ) ) );
+}
+
+void StdCategoryListView::removeCategory( int id )
+{
+ QListViewItem * item = items_map[ id ];
+
+ items_map.remove( id );
+ removeElement(item);
+}
+
+void StdCategoryListView::createCategory( const Element &category, int parent_id )
+{
+ CategoryListItem * new_item = 0;
+ if ( parent_id == -1 ) {
+ new_item = new CategoryListItem( this, category );
+ }
+ else {
+ CategoryListItem *parent = (CategoryListItem*)items_map[ parent_id ];
+
+ if ( parent ) {
+ if ( parent->isPopulated() )
+ new_item = new CategoryListItem( parent, category );
+ else if ( !parent->firstChild() ) {
+ new PseudoListItem( parent );
+ parent->setOpen(true);
+ }
+ }
+ }
+
+ if ( new_item ) {
+ items_map.insert( category.id, new_item );
+ new_item->setPixmap( 0, m_folder_icon );
+ createElement(new_item);//new QListViewItem(new_item);
+
+ CategoryTree list;
+ CategoryTree *p_list = &list;
+ database->loadCachedCategories( &p_list, 1, 0, category.id, false );
+
+ if ( p_list->firstChild() )
+ new PseudoListItem( new_item );
+ }
+}
+
+void StdCategoryListView::modCategory( QListViewItem* i )
+{
+ if ( i )
+ CategoryListView::rename( i, 0 );
+}
+
+void StdCategoryListView::saveCategory( QListViewItem* i )
+{
+ CategoryListItem * cat_it = ( CategoryListItem* ) i;
+
+ if ( !checkBounds( cat_it->categoryName() ) ) {
+ reload(ForceReload); //reset the changed text
+ return ;
+ }
+
+ int existing_id = database->findExistingCategoryByName( cat_it->categoryName() );
+ int cat_id = cat_it->categoryId();
+ if ( existing_id != -1 && existing_id != cat_id ) //category already exists with this label... merge the two
+ {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This category already exists. Continuing will merge these two categories into one. Are you sure?" ) ) )
+ {
+ case KMessageBox::Continue: {
+ database->mergeCategories( existing_id, cat_id );
+ break;
+ }
+ default:
+ reload(ForceReload);
+ break;
+ }
+ }
+ else
+ database->modCategory( cat_id, cat_it->categoryName() );
+}
+
+bool StdCategoryListView::checkBounds( const QString &name )
+{
+ if ( name.length() > uint(database->maxCategoryNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Category name cannot be longer than %1 characters." ) ).arg( database->maxCategoryNameLength() ) );
+ return false;
+ }
+
+ return true;
+}
+
+
+
+CategoryCheckListView::CategoryCheckListView( QWidget *parent, RecipeDB *db, bool _exclusive, const ElementList &init_items_checked ) : CategoryListView( parent, db ),
+ exclusive(_exclusive)
+{
+ addColumn( i18n( "Category" ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ for ( ElementList::const_iterator it = init_items_checked.begin(); it != init_items_checked.end(); ++it )
+ m_selections.append(*it);
+}
+
+void CategoryCheckListView::removeCategory( int id )
+{
+ QListViewItem * item = items_map[ id ];
+
+ items_map.remove( id );
+ removeElement(item);
+}
+
+void CategoryCheckListView::createCategory( const Element &category, int parent_id )
+{
+ CategoryCheckListItem * new_item = 0;
+ if ( parent_id == -1 ) {
+ new_item = new CategoryCheckListItem( this, category, exclusive );
+ }
+ else {
+ QListViewItem *parent = items_map[ parent_id ];
+ if ( parent )
+ new_item = new CategoryCheckListItem( parent, category, exclusive );
+ }
+
+ if ( new_item ) {
+ items_map.insert( category.id, new_item );
+ createElement(new_item);
+
+ CategoryTree list;
+ CategoryTree *p_list = &list;
+ database->loadCachedCategories( &p_list, 1, 0, category.id, false );
+
+ if ( p_list->firstChild() )
+ new PseudoListItem( new_item );
+
+
+ new_item->setOpen( false );
+ }
+}
+
+void CategoryCheckListView::stateChange( CategoryCheckListItem* it, bool on )
+{
+ if ( !reloading() ) {
+ if ( on )
+ m_selections.append(it->element());
+ else
+ m_selections.remove(it->element());
+ }
+}
+
+void CategoryCheckListView::load( int limit, int offset )
+{
+ CategoryListView::load(limit,offset);
+
+ for ( QValueList<Element>::const_iterator it = m_selections.begin(); it != m_selections.end(); ++it ) {
+ QCheckListItem * item = ( QCheckListItem* ) findItem( QString::number( (*it).id ), 1 );
+ if ( item ) {
+ item->setOn(true);
+ }
+ }
+}
+
+#include "categorylistview.moc"
diff --git a/krecipes/src/widgets/categorylistview.h b/krecipes/src/widgets/categorylistview.h
new file mode 100644
index 0000000..6b894a5
--- /dev/null
+++ b/krecipes/src/widgets/categorylistview.h
@@ -0,0 +1,288 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CATEGORYLISTVIEW_H
+#define CATEGORYLISTVIEW_H
+
+#include <qmap.h>
+#include <qpixmap.h>
+
+#include "dblistviewbase.h"
+
+#include "datablocks/elementlist.h"
+
+class KPopupMenu;
+
+class RecipeDB;
+class CategoryTree;
+class CategoryCheckListView;
+
+#define CATEGORYCHECKLISTITEM_RTTI 1005
+#define CATEGORYLISTITEM_RTTI 1001
+#define PSEUDOLISTITEM_RTTI 1008
+
+/** Category listitems inherit this class to provide a common interface for accessing this information.
+ */
+class CategoryItemInfo
+{
+public:
+ CategoryItemInfo( const Element &category ) : ctyStored( category ), populated(false){}
+ bool isPopulated() const { return populated; }
+ void setPopulated( bool b ){ populated = b; }
+
+ Element element() const
+ {
+ return ctyStored;
+ }
+
+ int categoryId() const
+ {
+ return ctyStored.id;
+ }
+ QString categoryName() const
+ {
+ return ctyStored.name;
+ }
+
+protected:
+ Element ctyStored;
+
+private:
+ bool populated;
+};
+
+class CategoryCheckListItem : public QCheckListItem, public CategoryItemInfo
+{
+public:
+ CategoryCheckListItem( CategoryCheckListView* klv, const Element &category, bool exclusive = true );
+ CategoryCheckListItem( QListViewItem* it, const Element &category, bool exclusive = true );
+ CategoryCheckListItem( CategoryCheckListView* klv, QListViewItem* it, const Element &category, bool exclusive = true );
+
+ virtual QString text( int column ) const;
+ virtual void setText( int column, const QString &text );
+
+ int rtti() const
+ {
+ return CATEGORYCHECKLISTITEM_RTTI;
+ }
+
+protected:
+ virtual void stateChange( bool );
+ void setChildrenState( bool );
+ void setParentsState( bool );
+
+ bool locked;
+ bool exclusive;
+
+private:
+ CategoryCheckListView *m_listview;
+};
+
+
+class CategoryListItem : public QListViewItem, public CategoryItemInfo
+{
+public:
+ CategoryListItem( QListView* klv, const Element &category );
+ CategoryListItem( QListViewItem* it, const Element &category );
+ CategoryListItem( QListView* klv, QListViewItem* it, const Element &category );
+
+ virtual QString text( int column ) const;
+ virtual void setText( int column, const QString &text );
+
+ int rtti() const
+ {
+ return CATEGORYLISTITEM_RTTI;
+ }
+};
+
+
+
+class CategoryListView : public DBListViewBase
+{
+ Q_OBJECT
+
+public:
+ CategoryListView( QWidget *parent, RecipeDB * );
+
+ void populateAll( QListViewItem *parent = 0 );
+
+public slots:
+ void open( QListViewItem *item );
+
+protected:
+ virtual void init();
+
+ virtual void load( int limit, int offset );
+
+ /** so that it allows dropping into
+ * subchildren that aren't expandable. The code is taken from KDE's KListView with
+ * one line commented out.
+ */
+ void findDrop( const QPoint &pos, QListViewItem *&parent, QListViewItem *&after )
+ {
+ QPoint p ( contentsToViewport( pos ) );
+
+ // Get the position to put it in
+ QListViewItem *atpos = itemAt( p );
+
+ QListViewItem *above;
+ if ( !atpos ) // put it at the end
+ above = lastItem();
+ else {
+ // Get the closest item before us ('atpos' or the one above, if any)
+ if ( p.y() - itemRect( atpos ).topLeft().y() < ( atpos->height() / 2 ) )
+ above = atpos->itemAbove();
+ else
+ above = atpos;
+ }
+
+ if ( above ) {
+ // if above has children, I might need to drop it as the first item there
+
+ if ( above->firstChild() && above->isOpen() ) {
+ parent = above;
+ after = 0;
+ return ;
+ }
+
+ // Now, we know we want to go after "above". But as a child or as a sibling ?
+ // We have to ask the "above" item if it accepts children.
+ // ### NOTE: Here is the one line commented out so that "above" always accepts children
+ //if (above->isExpandable())
+ {
+ // The mouse is sufficiently on the right ? - doesn't matter if 'above' has visible children
+ if ( p.x() >= depthToPixels( above->depth() + 1 ) ||
+ ( above->isOpen() && above->childCount() > 0 ) )
+ {
+ parent = above;
+ after = 0L;
+ return ;
+ }
+ }
+
+ // Ok, there's one more level of complexity. We may want to become a new
+ // sibling, but of an upper-level group, rather than the "above" item
+ QListViewItem * betterAbove = above->parent();
+ QListViewItem * last = above;
+ while ( betterAbove ) {
+ // We are allowed to become a sibling of "betterAbove" only if we are
+ // after its last child
+ if ( last->nextSibling() == 0 ) {
+ if ( p.x() < depthToPixels ( betterAbove->depth() + 1 ) )
+ above = betterAbove; // store this one, but don't stop yet, there may be a better one
+ else
+ break; // not enough on the left, so stop
+ last = betterAbove;
+ betterAbove = betterAbove->parent(); // up one level
+ }
+ else
+ break; // we're among the child of betterAbove, not after the last one
+ }
+ }
+ // set as sibling
+ after = above;
+ parent = after ? after->parent() : 0L ;
+ }
+
+protected slots:
+ virtual void removeCategory( int id ) = 0;
+ virtual void createCategory( const Element &category, int parent_id ) = 0;
+ virtual void modifyCategory( const Element &category );
+ virtual void modifyCategory( int id, int parent_id );
+ virtual void mergeCategories( int id1, int id2 );
+
+ virtual void checkCreateCategory( const Element &, int parent_id );
+ virtual void populate( QListViewItem *item );
+
+ QMap<int, QListViewItem*> items_map;
+
+private:
+ QListViewItem *m_item_to_delete;
+};
+
+
+class StdCategoryListView : public CategoryListView
+{
+ Q_OBJECT
+
+public:
+ StdCategoryListView( QWidget *parent, RecipeDB *, bool editable = false );
+ ~StdCategoryListView();
+
+protected:
+ virtual void removeCategory( int id );
+ virtual void createCategory( const Element &category, int parent_id );
+
+ void setPixmap( const QPixmap &pixmap );
+
+private slots:
+ void preparePopup();
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void createNew();
+ void remove
+ ();
+ void rename();
+ void cut();
+ void paste();
+ void pasteAsSub();
+
+ void changeCategoryParent( QListViewItem *item, QListViewItem * /*afterFirst*/, QListViewItem * /*afterNow*/ );
+
+ void modCategory( QListViewItem* i );
+ void saveCategory( QListViewItem* i );
+
+private:
+ bool checkBounds( const QString &name );
+
+ KPopupMenu *kpop;
+ QListViewItem *clipboard_item;
+ QListViewItem *clipboard_parent;
+
+ QPixmap m_folder_icon;
+};
+
+
+class CategoryCheckListView : public CategoryListView
+{
+ Q_OBJECT
+
+public:
+ CategoryCheckListView( QWidget *parent, RecipeDB *, bool exclusive=true, const ElementList &init_items_checked = ElementList() );
+
+ virtual void stateChange( CategoryCheckListItem*, bool );
+
+ ElementList selections() const{ return m_selections; }
+
+protected:
+ virtual void removeCategory( int id );
+ virtual void createCategory( const Element &category, int parent_id );
+
+ virtual void load( int limit, int offset );
+
+ bool exclusive;
+
+private:
+ ElementList m_selections;
+};
+
+
+class PseudoListItem : public QListViewItem
+{
+public:
+ PseudoListItem( QListView* lv ) : QListViewItem(lv){}
+ PseudoListItem( QListViewItem* it ) : QListViewItem(it){}
+
+protected:
+ int rtti() const { return PSEUDOLISTITEM_RTTI; }
+};
+
+#endif //CATEGORYLISTVIEW_H
diff --git a/krecipes/src/widgets/conversiontable.cpp b/krecipes/src/widgets/conversiontable.cpp
new file mode 100644
index 0000000..07abf0c
--- /dev/null
+++ b/krecipes/src/widgets/conversiontable.cpp
@@ -0,0 +1,426 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* Copyright (C) 2003-2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "conversiontable.h"
+#include "datablocks/mixednumber.h"
+#include "widgets/fractioninput.h"
+
+#include <qtooltip.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+
+class ConversionTableToolTip : public QToolTip
+{
+public:
+ ConversionTableToolTip( ConversionTable *t ) : QToolTip( t->viewport() ),
+ table( t )
+ {}
+
+ void maybeTip( const QPoint &pos )
+ {
+ if ( !table )
+ return ;
+
+ QPoint cp = table->viewportToContents( pos );
+
+ int row = table->rowAt( cp.y() );
+ int col = table->columnAt( cp.x() );
+
+ if ( row == col )
+ return ;
+
+ QString row_unit = table->verticalHeader() ->label( row );
+ QString col_unit = table->horizontalHeader() ->label( col );
+ QString text = table->text( row, col );
+ if ( text.isEmpty() )
+ text = "X"; //### Is this i18n friendly???
+
+ QRect cr = table->cellGeometry( row, col );
+ cr.moveTopLeft( table->contentsToViewport( cr.topLeft() ) );
+ tip( cr, QString( "1 %1 = %2 %3" ).arg( row_unit ).arg( text ).arg( col_unit ) );
+ }
+
+private:
+ ConversionTable *table;
+};
+
+ConversionTable::ConversionTable( QWidget* parent, int maxrows, int maxcols ) : QTable( maxrows, maxcols, parent, "table" )
+{
+ editBoxValue = -1;
+ items.setAutoDelete( true );
+ widgets.setAutoDelete( true );
+
+ ( void ) new ConversionTableToolTip( this );
+}
+
+ConversionTable::~ConversionTable()
+{}
+#include <kdebug.h>
+void ConversionTable::unitRemoved( int id )
+{
+ int index = *unitIDs.find( id );
+ kdDebug() << "index:" << index << endl;
+ removeRow( index );
+ removeColumn( index );
+ kdDebug() << "done" << endl;
+}
+
+void ConversionTable::unitCreated( const Unit &unit )
+{
+ insertColumns( numCols() );
+ insertRows( numRows() );
+ unitIDs.append( unit.id );
+ horizontalHeader() ->setLabel( numRows() - 1, unit.name );
+ verticalHeader() ->setLabel( numCols() - 1, unit.name );
+}
+
+QTableItem* ConversionTable::item( int r, int c ) const
+{
+ return items.find( indexOf( r, c ) );
+}
+
+void ConversionTable::setItem( int r, int c, QTableItem *i )
+{
+ items.replace( indexOf( r, c ), i );
+ i->setRow( r ); // Otherwise the item
+ i->setCol( c ); //doesn't know where it is!
+ updateCell( r, c );
+}
+
+void ConversionTable::clearCell( int r, int c )
+{
+ items.remove( indexOf( r, c ) );
+}
+
+void ConversionTable::takeItem( QTableItem *item )
+{
+ items.setAutoDelete( false );
+ items.remove( indexOf( item->row(), item->col() ) );
+ items.setAutoDelete( true );
+}
+
+void ConversionTable::insertWidget( int r, int c, QWidget *w )
+{
+ widgets.replace( indexOf( r, c ), w );
+}
+
+QWidget* ConversionTable::cellWidget( int r, int c ) const
+{
+ return widgets.find( indexOf( r, c ) );
+}
+
+void ConversionTable::clearCellWidget( int r, int c )
+{
+ QWidget * w = widgets.take( indexOf( r, c ) );
+ if ( w )
+ w->deleteLater();
+}
+
+
+ConversionTableItem::ConversionTableItem( QTable *t, EditType et ) : QTableItem( t, et, QString::null )
+{
+ //wedonotwantthisitemtobereplaced
+ setReplaceable( false );
+}
+
+void ConversionTableItem::paint( QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected )
+{
+ QColorGroup g( cg );
+
+ // Draw in gray all those cells which are not editable
+
+ if ( row() == col() )
+ g.setColor( QColorGroup::Base, gray );
+ QTableItem::paint( p, g, cr, selected );
+}
+
+QWidget* ConversionTableItem::createEditor() const
+{
+ FractionInput *editor = new FractionInput( table()->viewport(), MixedNumber::DecimalFormat );
+
+ MixedNumber current = MixedNumber::fromString(text());
+ if ( current.toDouble() > 1e-8 )
+ editor->setValue( current, 0 );
+
+ return editor;
+}
+
+void ConversionTableItem::setContentFromEditor( QWidget *w )
+{
+ // theuser changed the value of the combobox, so synchronize the
+ // value of the item (its text), with the value of the combobox
+ if ( w->inherits( "FractionInput" ) ) {
+ FractionInput* editor = ( FractionInput* ) w;
+ if ( editor->isInputValid() && !editor->isEmpty() && editor->value().toDouble() > 1e-6 ) {
+ setText( editor->value().toString(MixedNumber::DecimalFormat) );
+ emit ratioChanged( row(), col(), editor->value().toDouble() ); // Signal to store
+ }
+ else {
+ setText( QString::null );
+ emit ratioRemoved( row(), col() );
+ }
+ }
+ else
+ QTableItem::setContentFromEditor( w );
+}
+
+void ConversionTableItem::setText( const QString &s )
+{
+ QTableItem::setText( s );
+}
+QString ConversionTable::text( int r, int c ) const // without this function, the usual (text(r,c)) won't work
+{
+ if ( item( r, c ) )
+ return item( r, c ) ->text(); //Note that item(r,c) was reimplemented here for large sparse tables...
+ else
+ return QString::null;
+}
+
+void ConversionTable::initTable()
+{
+
+ for ( int r = 0;r < numRows();r++ ) {
+ this->createNewItem( r, r, 1.0 );
+ item( r, r ) ->setEnabled( false ); // Diagonal is not editable
+ }
+}
+
+void ConversionTable::createNewItem( int r, int c, double amount )
+{
+
+ ConversionTableItem * ci = new ConversionTableItem( this, QTableItem::WhenCurrent );
+ ci->setText( beautify( KGlobal::locale() ->formatNumber( amount, 5 ) ) );
+ setItem( r, c, ci );
+ // connect signal (forward) to know when it's actually changed
+ connect( ci, SIGNAL( ratioChanged( int, int, double ) ), this, SIGNAL( ratioChanged( int, int, double ) ) );
+ connect( ci, SIGNAL( ratioRemoved( int, int ) ), this, SIGNAL( ratioRemoved( int, int ) ) );
+ connect( ci, SIGNAL( signalRepaintCell( int, int ) ), this, SLOT( repaintCell( int, int ) ) );
+}
+
+void ConversionTable::setUnitIDs( const IDList &idList )
+{
+ unitIDs = idList;
+}
+
+void ConversionTable::setRatio( int ingID1, int ingID2, double ratio )
+{
+ int indexID1 = unitIDs.findIndex( ingID1 );
+ int indexID2 = unitIDs.findIndex( ingID2 );
+
+ createNewItem( indexID1, indexID2, ratio );
+}
+
+
+int ConversionTable::getUnitID( int rc )
+{
+ return ( *( unitIDs.at( rc ) ) );
+}
+
+QWidget * ConversionTable::beginEdit ( int row, int col, bool replace )
+{
+ // If there's no item, create it first.
+ if ( !item( row, col ) ) {
+ createNewItem( row, col, 0 );
+ }
+
+ // Then call normal beginEdit
+ return QTable::beginEdit( row, col, replace );
+}
+
+void ConversionTableItem::setTextAndSave( const QString &s )
+{
+ setText( s ); // Change text
+ emit signalRepaintCell( row(), col() ); // Indicate to update the cell to the table. Otherwise it's not repainted
+ emit ratioChanged( row(), col(), s.toDouble() ); // Signal to store
+}
+
+void ConversionTable::repaintCell( int r, int c )
+{
+ QTable::updateCell( r, c );
+}
+
+void ConversionTable::resize( int r, int c )
+{
+ setNumRows( r );
+ setNumCols( c );
+ initTable();
+}
+
+void ConversionTable::clear( void )
+{
+ items.clear();
+ widgets.clear();
+ unitIDs.clear();
+ resize( 0, 0 );
+
+}
+
+//TODO this is incomplete/wrong
+void ConversionTable::swapRows( int row1, int row2, bool /*swapHeader*/ )
+{
+ //if ( swapHeader )
+ //((QTableHeader*)verticalHeader())->swapSections( row1, row2, FALSE );
+
+ QPtrVector<QTableItem> tmpContents;
+ tmpContents.resize( numCols() );
+ QPtrVector<QWidget> tmpWidgets;
+ tmpWidgets.resize( numCols() );
+ int i;
+
+ items.setAutoDelete( FALSE );
+ widgets.setAutoDelete( FALSE );
+ for ( i = 0; i < numCols(); ++i ) {
+ QTableItem *i1, *i2;
+ i1 = item( row1, i );
+ i2 = item( row2, i );
+ if ( i1 || i2 ) {
+ tmpContents.insert( i, i1 );
+ items.remove( indexOf( row1, i ) );
+ items.insert( indexOf( row1, i ), i2 );
+ items.remove( indexOf( row2, i ) );
+ items.insert( indexOf( row2, i ), tmpContents[ i ] );
+ if ( items[ indexOf( row1, i ) ] )
+ items[ indexOf( row1, i ) ] ->setRow( row1 );
+ if ( items[ indexOf( row2, i ) ] )
+ items[ indexOf( row2, i ) ] ->setRow( row2 );
+ }
+
+ QWidget *w1, *w2;
+ w1 = cellWidget( row1, i );
+ w2 = cellWidget( row2, i );
+ if ( w1 || w2 ) {
+ tmpWidgets.insert( i, w1 );
+ widgets.remove( indexOf( row1, i ) );
+ widgets.insert( indexOf( row1, i ), w2 );
+ widgets.remove( indexOf( row2, i ) );
+ widgets.insert( indexOf( row2, i ), tmpWidgets[ i ] );
+ }
+ }
+ items.setAutoDelete( FALSE );
+ widgets.setAutoDelete( TRUE );
+
+ //updateRowWidgets( row1 );
+ //updateRowWidgets( row2 );
+ /*
+ if ( curRow == row1 )
+ curRow = row2;
+ else if ( curRow == row2 )
+ curRow = row1;
+ if ( editRow == row1 )
+ editRow = row2;
+ else if ( editRow == row2 )
+ editRow = row1;*/
+}
+
+//TODO this is incomplete/wrong
+void ConversionTable::swapColumns( int col1, int col2, bool /*swapHeader*/ )
+{
+ //if ( swapHeader )
+ //((QTableHeader*)horizontalHeader())->swapSections( col1, col2, FALSE );
+
+ QPtrVector<QTableItem> tmpContents;
+ tmpContents.resize( numRows() );
+ QPtrVector<QWidget> tmpWidgets;
+ tmpWidgets.resize( numRows() );
+ int i;
+
+ items.setAutoDelete( FALSE );
+ widgets.setAutoDelete( FALSE );
+ for ( i = 0; i < numRows(); ++i ) {
+ QTableItem *i1, *i2;
+ i1 = item( i, col1 );
+ i2 = item( i, col2 );
+ if ( i1 || i2 ) {
+ tmpContents.insert( i, i1 );
+ items.remove( indexOf( i, col1 ) );
+ items.insert( indexOf( i, col1 ), i2 );
+ items.remove( indexOf( i, col2 ) );
+ items.insert( indexOf( i, col2 ), tmpContents[ i ] );
+ if ( items[ indexOf( i, col1 ) ] )
+ items[ indexOf( i, col1 ) ] ->setCol( col1 );
+ if ( items[ indexOf( i, col2 ) ] )
+ items[ indexOf( i, col2 ) ] ->setCol( col2 );
+ }
+
+ QWidget *w1, *w2;
+ w1 = cellWidget( i, col1 );
+ w2 = cellWidget( i, col2 );
+ if ( w1 || w2 ) {
+ tmpWidgets.insert( i, w1 );
+ widgets.remove( indexOf( i, col1 ) );
+ widgets.insert( indexOf( i, col1 ), w2 );
+ widgets.remove( indexOf( i, col2 ) );
+ widgets.insert( indexOf( i, col2 ), tmpWidgets[ i ] );
+ }
+ }
+ items.setAutoDelete( FALSE );
+ widgets.setAutoDelete( TRUE );
+
+ columnWidthChanged( col1 );
+ columnWidthChanged( col2 );
+ /*
+ if ( curCol == col1 )
+ curCol = col2;
+ else if ( curCol == col2 )
+ curCol = col1;
+ if ( editCol == col1 )
+ editCol = col2;
+ else if ( editCol == col2 )
+ editCol = col1;*/
+}
+
+//TODO this is incomplete/wrong
+void ConversionTable::swapCells( int row1, int col1, int row2, int col2 )
+{
+ items.setAutoDelete( FALSE );
+ widgets.setAutoDelete( FALSE );
+ QTableItem *i1, *i2;
+ i1 = item( row1, col1 );
+ i2 = item( row2, col2 );
+ if ( i1 || i2 ) {
+ QTableItem * tmp = i1;
+ items.remove( indexOf( row1, col1 ) );
+ items.insert( indexOf( row1, col1 ), i2 );
+ items.remove( indexOf( row2, col2 ) );
+ items.insert( indexOf( row2, col2 ), tmp );
+ if ( items[ indexOf( row1, col1 ) ] ) {
+ items[ indexOf( row1, col1 ) ] ->setRow( row1 );
+ items[ indexOf( row1, col1 ) ] ->setCol( col1 );
+ }
+ if ( items[ indexOf( row2, col2 ) ] ) {
+ items[ indexOf( row2, col2 ) ] ->setRow( row2 );
+ items[ indexOf( row2, col2 ) ] ->setCol( col2 );
+ }
+ }
+
+ QWidget *w1, *w2;
+ w1 = cellWidget( row1, col1 );
+ w2 = cellWidget( row2, col2 );
+ if ( w1 || w2 ) {
+ QWidget * tmp = w1;
+ widgets.remove( indexOf( row1, col1 ) );
+ widgets.insert( indexOf( row1, col1 ), w2 );
+ widgets.remove( indexOf( row2, col2 ) );
+ widgets.insert( indexOf( row2, col2 ), tmp );
+ }
+
+ //updateRowWidgets( row1 );
+ //updateRowWidgets( row2 );
+ //updateColWidgets( col1 );
+ //updateColWidgets( col2 );
+ items.setAutoDelete( FALSE );
+ widgets.setAutoDelete( TRUE );
+}
+
+#include "conversiontable.moc"
diff --git a/krecipes/src/widgets/conversiontable.h b/krecipes/src/widgets/conversiontable.h
new file mode 100644
index 0000000..ca3f85c
--- /dev/null
+++ b/krecipes/src/widgets/conversiontable.h
@@ -0,0 +1,101 @@
+/***************************************************************************
+* Copyright (C) 2003-2004 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef CONVERSIONTABLE_H
+#define CONVERSIONTABLE_H
+#include <qstring.h>
+#include <qtable.h>
+#include <qobject.h>
+
+#include "datablocks/unitratio.h"
+#include "datablocks/elementlist.h"
+#include "datablocks/unit.h"
+
+/**
+@author Unai Garro
+*/
+
+
+class ConversionTable: public QTable
+{
+ Q_OBJECT
+public:
+
+ ConversionTable( QWidget* parent, int maxrows, int maxcols );
+ ~ConversionTable();
+ void createNewItem( int r, int c, double amount );
+ void setUnitIDs( const IDList &idList );
+ void setRatio( int ingID1, int ingID2, double ratio );
+ void setRatio( const UnitRatio &r )
+ {
+ setRatio( r.uID1, r.uID2, r.ratio );
+ }
+ int getUnitID( int rc );
+ QString text( int r, int c ) const; //Reimplement, otherwise it won't work this way
+ void resize( int r, int c );
+ void clear( void );
+private:
+
+ //Internal Variables
+ double editBoxValue;
+ QIntDict<QTableItem> items;
+ QIntDict<QWidget> widgets;
+ IDList unitIDs; // unit ID list to know the units by ID, not name
+ //Internal Methods
+ void resizeData( int )
+ {}
+ ;
+ QTableItem *item( int r, int c ) const;
+ void setItem( int r, int c, QTableItem *i );
+ void clearCell( int r, int c );
+ void takeItem( QTableItem *item );
+ void insertWidget( int r, int c, QWidget *w );
+ QWidget *cellWidget( int r, int c ) const;
+ void clearCellWidget( int r, int c );
+ void initTable();
+ void swapRows( int, int, bool );
+ void swapColumns( int, int, bool );
+ void swapCells( int, int, int, int );
+protected:
+ QWidget* beginEdit ( int row, int col, bool replace );
+
+private slots:
+ void repaintCell( int r, int c );
+
+ void unitRemoved( int );
+ void unitCreated( const Unit& );
+signals:
+ void ratioChanged( int row, int col, double value );
+ void ratioRemoved( int row, int col );
+};
+
+class ConversionTableItem: public QObject, public QTableItem
+{
+ Q_OBJECT
+public:
+ ConversionTableItem( QTable *t, EditType et );
+ QWidget *createEditor() const;
+ void setContentFromEditor( QWidget *w );
+ void setText( const QString &s );
+ void paint( QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected );
+ void setTextAndSave( const QString &s );
+ int alignment() const
+ {
+ return Qt::AlignRight;
+ }
+signals:
+ void ratioChanged( int row, int col, double value );
+ void ratioRemoved( int row, int col );
+ void signalRepaintCell( int r, int c );
+
+};
+
+
+#endif
diff --git a/krecipes/src/widgets/criteriacombobox.cpp b/krecipes/src/widgets/criteriacombobox.cpp
new file mode 100644
index 0000000..1b037df
--- /dev/null
+++ b/krecipes/src/widgets/criteriacombobox.cpp
@@ -0,0 +1,49 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "criteriacombobox.h"
+
+#include <qlistbox.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/elementlist.h"
+
+CriteriaComboBox::CriteriaComboBox( bool b, QWidget *parent, RecipeDB *db ) : KComboBox( b, parent ),
+ database( db )
+{
+ connect( db, SIGNAL(ratingCriteriaCreated(const Element &)), this, SLOT(addCriteria(const Element &)) );
+}
+
+void CriteriaComboBox::addCriteria( const Element &criteria )
+{
+ idMap.insert(count(),criteria.id);
+
+ insertItem(criteria.name);
+ completionObject()->addItem(criteria.name);
+}
+
+void CriteriaComboBox::reload()
+{
+ ElementList criteriaList;
+ database->loadRatingCriterion( &criteriaList );
+
+ clear();
+
+ for ( ElementList::const_iterator it = criteriaList.begin(); it != criteriaList.end(); ++it ) {
+ addCriteria((*it));
+ }
+}
+
+int CriteriaComboBox::criteriaID( int index )
+{
+ return idMap[index];
+}
+
+#include "criteriacombobox.moc"
diff --git a/krecipes/src/widgets/criteriacombobox.h b/krecipes/src/widgets/criteriacombobox.h
new file mode 100644
index 0000000..b3ec8f4
--- /dev/null
+++ b/krecipes/src/widgets/criteriacombobox.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef CRITERIACOMBOBOX_H
+#define CRITERIACOMBOBOX_H
+
+#include <qmap.h>
+
+#include <kcombobox.h>
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+
+class CriteriaComboBox : public KComboBox
+{
+ Q_OBJECT
+
+public:
+ CriteriaComboBox( bool, QWidget *parent, RecipeDB *db );
+
+ void reload();
+ int criteriaID( int index );
+
+protected slots:
+ void addCriteria( const Element &criteria );
+
+private:
+ RecipeDB *database;
+ QMap< int, int > idMap;
+};
+
+#endif //CRITERIACOMBOBOX_H
+
diff --git a/krecipes/src/widgets/dblistviewbase.cpp b/krecipes/src/widgets/dblistviewbase.cpp
new file mode 100644
index 0000000..0be4f38
--- /dev/null
+++ b/krecipes/src/widgets/dblistviewbase.cpp
@@ -0,0 +1,334 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "dblistviewbase.h"
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kcursor.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kprogress.h>
+
+
+//These two classes are used to identify the "Next" and "Prev" items, which are identified through rtti(). This also prevents renaming, even if it is enabled.
+class PrevListViewItem : public QListViewItem
+{
+public:
+ PrevListViewItem( QListView *parent ) : QListViewItem(parent){}
+
+ virtual int rtti() const { return PREVLISTITEM_RTTI; }
+
+ QString text( int c ) const {
+ if ( c == 0 ) {
+ return QString("<< %1").arg(i18n("Previous"));
+ }
+ else
+ return QString::null;
+ }
+};
+
+class NextListViewItem : public QListViewItem
+{
+public:
+ NextListViewItem( QListView *parent, QListViewItem *after ) : QListViewItem(parent,after){}
+
+ virtual int rtti() const { return NEXTLISTITEM_RTTI; }
+
+ QString text( int c ) const {
+ if ( c == 0 ) {
+ return QString("%1 >>").arg(i18n("Next"));
+ }
+ else
+ return QString::null;
+ }
+};
+
+DBListViewBase::DBListViewBase( QWidget *parent, RecipeDB *db, int t ) : KListView(parent),
+ database(db),
+ curr_limit(-1),
+ curr_offset(0),
+ total(t),
+ bulk_load(false),
+ delete_me_later(0),
+ m_progress(0),
+ m_totalSteps(0)
+{
+ setSorting(-1);
+
+ if ( curr_limit == -1 ) { //only use the default limit if a subclass hasn't given curr_limit its own value
+ KConfig * config = KGlobal::config();config->setGroup( "Performance" );
+ curr_limit = config->readNumEntry( "Limit", -1 );
+ }
+
+ connect(this,SIGNAL(executed(QListViewItem*)),SLOT(slotDoubleClicked(QListViewItem*)));
+}
+
+DBListViewBase::~DBListViewBase()
+{
+ delete m_progress;
+}
+
+void DBListViewBase::activatePrev()
+{
+ if ( curr_offset != 0 ) {
+ curr_offset -= curr_limit;
+ if ( curr_offset < 0 )
+ curr_offset = 0;
+
+ reload(ForceReload);
+ emit prevGroupLoaded();
+ }
+}
+
+void DBListViewBase::activateNext()
+{
+ curr_offset += curr_limit;
+
+ reload(ForceReload);
+ emit nextGroupLoaded();
+}
+
+void DBListViewBase::rename( QListViewItem *it, int c )
+{
+ if ( it->rtti() == PREVLISTITEM_RTTI || it->rtti() == NEXTLISTITEM_RTTI ) {
+ return;
+ }
+
+ KListView::rename(it,c);
+}
+
+void DBListViewBase::slotDoubleClicked( QListViewItem *it )
+{
+ //we can't delete the item the was double clicked
+ //and yet these functions will clear() the listview.
+ //We'll take the item from the view so it isn't deleted
+ //and delete it ourselves.
+ delete delete_me_later; delete_me_later = 0;
+
+ if ( it->rtti() == PREVLISTITEM_RTTI ) {
+ delete_me_later = it;
+ takeItem(it);
+ activatePrev();
+ }
+ else if ( it->rtti() == NEXTLISTITEM_RTTI ) {
+ delete_me_later = it;
+ takeItem(it);
+ activateNext();
+ }
+}
+
+void DBListViewBase::keyPressEvent( QKeyEvent *k )
+{
+ if ( k->state() == Qt::ShiftButton ) {
+ switch ( k->key() ) {
+ case Qt::Key_N: {
+ if ( curr_offset + curr_limit >= total || curr_limit == -1 ) {
+ k->accept();
+ return;
+ }
+
+ kapp->processEvents(); //if auto-repeating, user won't otherwise see change in the listview
+ activateNext();
+ k->accept();
+ break;
+ }
+ case Qt::Key_P: {
+ kapp->processEvents(); //if auto-repeating, user won't otherwise see change in the listview
+ activatePrev();
+ k->accept();
+ break;
+ }
+ default: break;
+ }
+ }
+
+ KListView::keyPressEvent(k);
+}
+
+void DBListViewBase::reload( ReloadFlags flag )
+{
+ if ( flag == ForceReload || (!firstChild() && flag == Load) || (firstChild() && flag == ReloadIfPopulated) ) {
+ KApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ init();
+
+ //m_progress = new KProgressDialog(this,0,QString::null,i18n("Loading..."),true);
+ //m_progress->setAllowCancel(false);
+ //m_progress->progressBar()->setPercentageVisible(false);
+ //m_progress->progressBar()->setTotalSteps( m_totalSteps );
+ //m_progress->show();
+ //kapp->processEvents();
+
+ //reset some things
+ clear();
+ lastElementMap.clear();
+
+ bulk_load=true;
+ load(curr_limit,curr_offset);
+ bulk_load=false;
+
+ if ( curr_limit != -1 && curr_offset + curr_limit < total )
+ new NextListViewItem(this,lastElementMap[0]);
+
+ if ( curr_offset != 0 )
+ new PrevListViewItem(this);
+
+ //delete m_progress; m_progress = 0;
+
+ KApplication::restoreOverrideCursor();
+ }
+}
+
+void DBListViewBase::setTotalItems(int i)
+{
+ m_totalSteps = i;
+ if ( m_progress ) {
+ m_progress->progressBar()->setTotalSteps( m_totalSteps );
+ }
+}
+
+void DBListViewBase::createElement( QListViewItem *it )
+{
+ Q_ASSERT(it);
+
+ QListViewItem *lastElement;
+ QMap<QListViewItem*,QListViewItem*>::iterator map_it = lastElementMap.find(it->parent());
+ if ( map_it != lastElementMap.end() ) {
+ lastElement = map_it.data();
+ }
+ else
+ lastElement = 0;
+
+ if ( bulk_load ) { //this can be much faster if we know the elements are already in order
+ if ( lastElement ) it->moveItem(lastElement);
+ lastElementMap.insert(it->parent(),it);
+ if ( m_progress ) { m_progress->progressBar()->advance(1); }
+ }
+ else {
+ if ( lastElement == 0 ) {
+ lastElementMap.insert(it->parent(),it);
+ }
+ else {
+
+ int c = 0;//FIXME: the column used should be variable (set by a subclass)
+
+ if ( it->parent() == 0 ) {
+ //start it out below the "Prev" item... currently it will be at firstChild()
+ if ( firstChild()->nextSibling() &&
+ ( firstChild()->nextSibling()->rtti() == PREVLISTITEM_RTTI ||
+ firstChild()->nextSibling()->rtti() == 1006 ) ) { //A hack to skip the Uncategorized item
+ it->moveItem( firstChild()->nextSibling() );
+ }
+ }
+
+ if ( QString::localeAwareCompare(it->text(c),lastElement->text(c)) >= 0 ) {
+ it->moveItem(lastElement);
+ lastElementMap.insert(it->parent(),it);
+ }
+ else {
+ QListViewItem *last_it = 0;
+
+ for ( QListViewItem *search_it = it; search_it; search_it = search_it->nextSibling() ) {
+ if ( search_it->rtti() == NEXTLISTITEM_RTTI ) {
+ it->moveItem(lastElement);
+ lastElementMap.insert(it->parent(),it);
+ }
+ else if ( QString::localeAwareCompare(it->text(c),search_it->text(c)) < 0 ) { //we assume the list is sorted, as it should stay
+ if ( last_it ) it->moveItem(last_it);
+ break;
+ }
+ last_it = search_it;
+ }
+ }
+ }
+ }
+}
+
+void DBListViewBase::removeElement( QListViewItem *it, bool delete_item )
+{
+ total--;
+ if ( !it ) return;
+
+ QListViewItem *lastElement = lastElementMap[it->parent()];
+ if ( it == lastElement ) {
+ for ( QListViewItem *search_it = (it->parent())?it->parent()->firstChild():firstChild(); search_it->nextSibling(); search_it = search_it->nextSibling() ) {
+ if ( it == search_it->nextSibling() ) {
+ lastElementMap.insert(it->parent(),search_it);
+ lastElement = search_it;
+ break;
+ }
+ }
+
+ if ( lastElement == it || lastElement->rtti() == PREVLISTITEM_RTTI ) { //there are no more items in the view if this happens
+ if ( firstChild() && firstChild()->rtti() == PREVLISTITEM_RTTI ) {
+ activatePrev();
+ it = 0; //keep 'delete it' below from segfault'ing
+ }
+ else if ( lastElement->nextSibling() && lastElement->nextSibling()->rtti() == NEXTLISTITEM_RTTI ) {
+ reload();
+ it = 0; //keep 'delete it' below from segfault'ing
+ }
+ else //the list is now empty, there is no last element
+ lastElementMap.remove(it->parent());
+ }
+ }
+
+ if ( delete_item )
+ delete it;
+}
+
+bool DBListViewBase::handleElement( const QString &name )
+{
+ total++;
+
+ QListViewItem *lastElement = lastElementMap[0];
+
+ int c = 0;//FIXME: the column used should be variable (set by a subclass)
+
+ int child_count = childCount();
+ if ( child_count == 0 ) return true;
+
+ if ( firstChild()->rtti() == PREVLISTITEM_RTTI || firstChild()->rtti() == 1006 ){ child_count--; } //"Prev" item
+ if ( child_count == 0 ) return true;
+
+ if ( lastElement->nextSibling() ){ child_count--; } //"Next" item
+
+ if ( curr_limit != -1 && child_count >= curr_limit ) {
+ QListViewItem *firstElement = firstChild();
+ if (firstElement->rtti() == PREVLISTITEM_RTTI || firstElement->rtti() == 1006 ) {
+ firstElement = firstElement->nextSibling();
+ }
+ else if ( name < firstElement->text(c) ) { //provide access to this new element if we need to
+ new PrevListViewItem(this);
+ curr_offset++;
+ return false;
+ }
+
+ if ( name < firstElement->text(c) ) {
+ curr_offset++;
+ return false;
+ }
+ else if ( name >= lastElement->text(c) ) {
+ if ( lastElement->nextSibling() == 0 )
+ new NextListViewItem(this,lastElement);
+
+ return false;
+ }
+ else {
+ removeElement(lastElement);
+ }
+ }
+
+ return true;
+}
+
+#include "dblistviewbase.moc"
diff --git a/krecipes/src/widgets/dblistviewbase.h b/krecipes/src/widgets/dblistviewbase.h
new file mode 100644
index 0000000..2db4fe6
--- /dev/null
+++ b/krecipes/src/widgets/dblistviewbase.h
@@ -0,0 +1,89 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef LISTVIEWHANDLER_H
+#define LISTVIEWHANDLER_H
+
+#include <qobject.h>
+#include <qmap.h>
+
+#include <klistview.h>
+
+#define PREVLISTITEM_RTTI 1002
+#define NEXTLISTITEM_RTTI 1003
+
+class KProgressDialog;
+
+class RecipeDB;
+
+typedef enum ReloadFlags {
+ Load, /** Only performs the reload if the list hasn't already been loaded */
+ ReloadIfPopulated, /** Only performs the reload if the list has been loaded */
+ ForceReload /** Load/reload the list regardless of whether or not it's been loaded */
+};
+
+class DBListViewBase : public KListView
+{
+Q_OBJECT
+
+public:
+ DBListViewBase( QWidget *, RecipeDB *, int total );
+ ~DBListViewBase();
+
+ void reload( ReloadFlags flag = Load );
+
+signals:
+ void nextGroupLoaded();
+ void prevGroupLoaded();
+
+protected:
+ /**
+ * Called when the list view is ready to be used, i.e., it has been loaded with data.
+ * Until the list view has been loaded, we can ignore all database signals regarding changes
+ * of data. Therefore, subclasses should connect to these signals during this call.
+ */
+ virtual void init(){}
+ virtual void load(int limit, int offset) = 0;
+ virtual void keyPressEvent( QKeyEvent *e );
+ bool handleElement( const QString & );
+ virtual void createElement( QListViewItem * );
+ void removeElement( QListViewItem *, bool delete_item = true );
+
+ bool reloading(){ return bulk_load; }
+ void setSorting(int c){KListView::setSorting(c);} //don't do sorting, the database comes sorted from the database anyways
+ void setTotalItems(int);
+
+ RecipeDB *database;
+ int curr_limit;
+ int curr_offset;
+
+protected slots:
+ void rename( QListViewItem *, int c );
+ void slotDoubleClicked( QListViewItem * );
+
+private:
+ void activatePrev();
+ void activateNext();
+
+ //make this private because the data should always be synced with the database
+ void clear(){KListView::clear();}
+
+ int total;
+
+ bool bulk_load;
+
+ QMap<QListViewItem*,QListViewItem*> lastElementMap;
+ QListViewItem *delete_me_later;
+
+ KProgressDialog *m_progress;
+ int m_totalSteps;
+};
+
+#endif //LISTVIEWHANDLER_H
diff --git a/krecipes/src/widgets/fractioninput.cpp b/krecipes/src/widgets/fractioninput.cpp
new file mode 100644
index 0000000..e9ceac1
--- /dev/null
+++ b/krecipes/src/widgets/fractioninput.cpp
@@ -0,0 +1,121 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "fractioninput.h"
+
+#include <qtimer.h>
+
+#include <kglobalsettings.h>
+
+#include "datablocks/ingredient.h"
+
+FractionInput::FractionInput( QWidget *parent, MixedNumber::Format format ) : KLineEdit( parent ),
+ m_allowRange(false),
+ m_validateTimer(new QTimer(this)),
+ m_format(format)
+{
+ setAlignment( Qt::AlignRight );
+
+ connect( this, SIGNAL(textChanged(const QString&)), this, SLOT(slotStartValidateTimer()) );
+ connect( m_validateTimer, SIGNAL(timeout()), this, SLOT(validate()) );
+}
+
+FractionInput::~FractionInput()
+{
+ delete m_validateTimer;
+}
+
+void FractionInput::setValue( double d, double amount_offset )
+{
+ MixedNumber m( d );
+ setValue( m, amount_offset );
+}
+
+void FractionInput::setValue( const MixedNumber &m, double amount_offset )
+{
+ QString text = m.toString( m_format );
+ if ( amount_offset > 0 ) {
+ text += "-" + MixedNumber(m+amount_offset).toString( MixedNumber::MixedNumberFormat );
+ }
+ setText(text);
+}
+
+void FractionInput::value( MixedNumber &amount, double &amount_offset ) const
+{
+ Ingredient i; i.setAmount( text() );
+
+ amount = MixedNumber(i.amount);
+ amount_offset = i.amount_offset;
+}
+
+void FractionInput::value( double &amount, double &amount_offset ) const
+{
+ Ingredient i; i.setAmount( text() );
+
+ amount = i.amount;
+ amount_offset = i.amount_offset;
+}
+
+MixedNumber FractionInput::value() const
+{
+ Ingredient i; i.setAmount( text() );
+
+ return MixedNumber(i.amount);
+}
+
+MixedNumber FractionInput::minValue() const
+{
+ Ingredient i; i.setAmount( text() );
+
+ return MixedNumber(i.amount);
+}
+
+MixedNumber FractionInput::maxValue() const
+{
+ Ingredient i; i.setAmount( text() );
+
+ return MixedNumber(i.amount_offset+i.amount);
+}
+
+bool FractionInput::isInputValid() const
+{
+ if ( !m_allowRange && text().contains("-") )
+ return false;
+
+ bool ok;
+ Ingredient i; i.setAmount( text(), &ok );
+
+ return ok;
+}
+
+void FractionInput::slotStartValidateTimer()
+{
+ if ( !m_validateTimer->isActive() )
+ m_validateTimer->start( 1000, true );
+
+ if ( isInputValid() )
+ emit valueChanged( value() );
+}
+
+void FractionInput::validate()
+{
+ if ( isInputValid() ) {
+ setPaletteForegroundColor( KGlobalSettings::textColor() );
+ }
+ else
+ setPaletteForegroundColor( Qt::red );
+}
+
+bool FractionInput::isEmpty() const
+{
+ return text().isEmpty();
+}
+
+#include "fractioninput.moc"
diff --git a/krecipes/src/widgets/fractioninput.h b/krecipes/src/widgets/fractioninput.h
new file mode 100644
index 0000000..6b49020
--- /dev/null
+++ b/krecipes/src/widgets/fractioninput.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef FRACTIONINPUT_H
+#define FRACTIONINPUT_H
+
+#include <klineedit.h>
+
+#include "datablocks/mixednumber.h"
+
+class QTimer;
+
+/** A KLineEdit widget extended to allow input of decimals and fractions or ranges of such.
+ * Input is returned as a @ref MixedNumber class.
+ * @author Jason Kivlighn
+ */
+class FractionInput : public KLineEdit
+{
+Q_OBJECT
+
+public:
+ FractionInput( QWidget *parent = 0, MixedNumber::Format = MixedNumber::MixedNumberFormat );
+ ~FractionInput();
+
+ void setAllowRange( bool b ){ m_allowRange = b; }
+
+ void setValue( double amount, double amount_offset );
+ void setValue( const MixedNumber &, double amount_offset );
+
+ void value( MixedNumber &amount, double &amount_offset ) const;
+ void value( double &amount, double &amount_offset ) const;
+ MixedNumber minValue() const;
+ MixedNumber maxValue() const;
+ MixedNumber value() const;
+
+ bool isInputValid() const;
+ bool isEmpty() const;
+
+signals:
+ void valueChanged( const MixedNumber & );
+
+public slots:
+ void validate();
+
+private slots:
+ void slotStartValidateTimer();
+
+private:
+ bool m_allowRange;
+ QTimer *m_validateTimer;
+ MixedNumber::Format m_format;
+};
+
+#endif //FRACTIONINPUT_H
+
diff --git a/krecipes/src/widgets/headercombobox.cpp b/krecipes/src/widgets/headercombobox.cpp
new file mode 100644
index 0000000..6f118c6
--- /dev/null
+++ b/krecipes/src/widgets/headercombobox.cpp
@@ -0,0 +1,42 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "headercombobox.h"
+
+#include <qlistbox.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/elementlist.h"
+
+HeaderComboBox::HeaderComboBox( bool b, QWidget *parent, RecipeDB *db ) : KComboBox( b, parent ),
+ database( db )
+{
+}
+
+void HeaderComboBox::reload()
+{
+ QString remember_text = currentText();
+
+ ElementList headerList;
+ database->loadIngredientGroups( &headerList );
+
+ clear();
+
+ for ( ElementList::const_iterator it = headerList.begin(); it != headerList.end(); ++it ) {
+ insertItem((*it).name);
+ completionObject()->addItem((*it).name);
+ }
+
+ if ( listBox()->findItem( remember_text, Qt::ExactMatch ) ) {
+ setCurrentText( remember_text );
+ }
+}
+
+#include "headercombobox.moc"
diff --git a/krecipes/src/widgets/headercombobox.h b/krecipes/src/widgets/headercombobox.h
new file mode 100644
index 0000000..09e47b8
--- /dev/null
+++ b/krecipes/src/widgets/headercombobox.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef HEADERCOMBOBOX_H
+#define HEADERCOMBOBOX_H
+
+#include <kcombobox.h>
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+
+class HeaderComboBox : public KComboBox
+{
+ Q_OBJECT
+
+public:
+ HeaderComboBox( bool, QWidget *parent, RecipeDB *db );
+
+ void reload();
+
+private:
+ RecipeDB *database;
+};
+
+#endif //HEADERCOMBOBOX_H
+
diff --git a/krecipes/src/widgets/headerlistview.cpp b/krecipes/src/widgets/headerlistview.cpp
new file mode 100644
index 0000000..3c9a379
--- /dev/null
+++ b/krecipes/src/widgets/headerlistview.cpp
@@ -0,0 +1,196 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "headerlistview.h"
+
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include "backends/recipedb.h"
+#include "dialogs/createelementdialog.h"
+#include "dialogs/dependanciesdialog.h"
+
+HeaderListView::HeaderListView( QWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db,db->unitCount() )
+{
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+}
+
+void HeaderListView::init()
+{
+ connect( database, SIGNAL( ingGroupCreated( const Element & ) ), SLOT( checkCreateHeader( const Element & ) ) );
+ connect( database, SIGNAL( ingGroupRemoved( int ) ), SLOT( removeHeader( int ) ) );
+}
+
+void HeaderListView::load( int /*limit*/, int /*offset*/ )
+{
+ ElementList headerList;
+ database->loadIngredientGroups( &headerList );
+
+ setTotalItems(headerList.count());
+
+ for ( ElementList::const_iterator it = headerList.begin(); it != headerList.end(); ++it ) {
+ createHeader( *it );
+ }
+}
+
+void HeaderListView::checkCreateHeader( const Element &el )
+{
+ if ( handleElement(el.name) ) { //only create this header if the base class okays it
+ createHeader(el);
+ }
+}
+
+
+StdHeaderListView::StdHeaderListView( QWidget *parent, RecipeDB *db, bool editable ) : HeaderListView( parent, db )
+{
+ addColumn( i18n( "Header" ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+
+ KIconLoader *il = new KIconLoader;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il->loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_C );
+ kpop->insertItem( il->loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->polish();
+
+ delete il;
+
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), this, SLOT( modHeader( QListViewItem*, const QPoint &, int ) ) );
+ connect( this, SIGNAL( itemRenamed( QListViewItem*, const QString &, int ) ), this, SLOT( saveHeader( QListViewItem*, const QString &, int ) ) );
+ }
+}
+
+void StdHeaderListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdHeaderListView::createNew()
+{
+ CreateElementDialog * headerDialog = new CreateElementDialog( this, i18n("Header") );
+
+ if ( headerDialog->exec() == QDialog::Accepted ) {
+ QString result = headerDialog->newElementName();
+
+ //check bounds first
+ if ( checkBounds( result ) )
+ database->createNewIngGroup( result );
+ }
+ delete headerDialog;
+}
+
+void StdHeaderListView::remove()
+{
+ // Find selected header item
+ QListViewItem * it = selectedItem();
+
+ if ( it ) {
+ int headerID = it->text( 1 ).toInt();
+
+ ElementList recipeDependancies;
+ database->findUseOfIngGroupInRecipes( &recipeDependancies, headerID );
+
+ if ( recipeDependancies.isEmpty() )
+ database->removeIngredientGroup( headerID );
+ else { // need warning!
+ ListInfo info;
+ info.list = recipeDependancies;
+ info.name = i18n( "Recipes" );
+
+ DependanciesDialog warnDialog( this, info );
+ warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") );
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removeIngredientGroup( headerID );
+ }
+ }
+}
+
+void StdHeaderListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item )
+ HeaderListView::rename( item, 0 );
+}
+
+void StdHeaderListView::createHeader( const Element &header )
+{
+ createElement(new QListViewItem( this, header.name, QString::number( header.id ) ));
+}
+
+void StdHeaderListView::removeHeader( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 1 );
+ removeElement(item);
+}
+
+void StdHeaderListView::modHeader( QListViewItem* i, const QPoint & /*p*/, int c )
+{
+ if ( i )
+ HeaderListView::rename( i, c );
+}
+
+void StdHeaderListView::saveHeader( QListViewItem* i, const QString &text, int /*c*/ )
+{
+ if ( !checkBounds( text ) ) {
+ reload(ForceReload); //reset the changed text
+ return ;
+ }
+
+ int existing_id = database->findExistingIngredientGroupByName( text );
+ int header_id = i->text( 1 ).toInt();
+ if ( existing_id != -1 && existing_id != header_id ) { //already exists with this label... merge the two
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This header already exists. Continuing will merge these two headers into one. Are you sure?" ) ) ) {
+ case KMessageBox::Continue: {
+ database->modIngredientGroup( header_id, i->text( 0 ) );
+ database->mergeIngredientGroups( header_id, existing_id );
+ break;
+ }
+ default:
+ reload(ForceReload);
+ break;
+ }
+ }
+ else {
+ database->modIngredientGroup( header_id, i->text( 0 ) );
+ }
+}
+
+bool StdHeaderListView::checkBounds( const QString &header )
+{
+ if ( header.length() > uint(database->maxIngGroupNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Header cannot be longer than %1 characters." ) ).arg( database->maxIngGroupNameLength() ) );
+ return false;
+ }
+ else if ( header.stripWhiteSpace().isEmpty() )
+ return false;
+
+ return true;
+}
+
+#include "headerlistview.moc"
diff --git a/krecipes/src/widgets/headerlistview.h b/krecipes/src/widgets/headerlistview.h
new file mode 100644
index 0000000..47a690e
--- /dev/null
+++ b/krecipes/src/widgets/headerlistview.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef HEADERLISTVIEW_H
+#define HEADERLISTVIEW_H
+
+#include "dblistviewbase.h"
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+class KPopupMenu;
+
+class HeaderListView : public DBListViewBase
+{
+ Q_OBJECT
+
+public:
+ HeaderListView( QWidget *parent, RecipeDB *db );
+
+public slots:
+ virtual void load( int curr_limit, int curr_offset );
+
+protected slots:
+ virtual void createHeader( const Element & ) = 0;
+ virtual void removeHeader( int ) = 0;
+
+ void checkCreateHeader( const Element &el );
+
+protected:
+ virtual void init();
+};
+
+class StdHeaderListView : public HeaderListView
+{
+ Q_OBJECT
+
+public:
+ StdHeaderListView( QWidget *parent, RecipeDB *db, bool editable = false );
+
+protected:
+ virtual void createHeader( const Element & );
+ virtual void removeHeader( int );
+
+private slots:
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void createNew();
+ void remove();
+ void rename();
+
+ void modHeader( QListViewItem* i, const QPoint &p, int c );
+ void saveHeader( QListViewItem* i, const QString &text, int c );
+
+private:
+ bool checkBounds( const QString &unit );
+
+ KPopupMenu *kpop;
+};
+
+#endif //HEADERLISTVIEW_H
diff --git a/krecipes/src/widgets/inglistviewitem.cpp b/krecipes/src/widgets/inglistviewitem.cpp
new file mode 100644
index 0000000..08a41c7
--- /dev/null
+++ b/krecipes/src/widgets/inglistviewitem.cpp
@@ -0,0 +1,225 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "inglistviewitem.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kdebug.h>
+
+#include "datablocks/unit.h"
+#include "datablocks/mixednumber.h"
+
+IngSubListViewItem::IngSubListViewItem( QListViewItem* qli, const Ingredient &i ) : IngListViewItem( qli, 0, i )
+{
+}
+
+QString IngSubListViewItem::text( int column ) const
+{
+ if ( column == 0 ) {
+ //kdDebug()<<"displaying col 0 for "<<m_ing.name<<endl;
+ return QString("%1 ").arg(i18n("OR"))+m_ing.name;
+ //return m_ing.name;
+ }
+ else
+ return IngListViewItem::text(column);
+
+}
+
+void IngSubListViewItem::setText( int column, const QString &text )
+{
+ switch ( column ) {
+ case 0: {
+ QString compare = QString("%1 ").arg(i18n("OR"));
+ if ( text.left(compare.length()) == compare )
+ m_ing.name = text.right(text.length()-compare.length());
+ else
+ m_ing.name = text;
+ break;
+ }
+ default:
+ IngListViewItem::setText(column,text);
+ break;
+ }
+}
+
+int IngSubListViewItem::rtti() const
+{
+ return INGSUBLISTVIEWITEM_RTTI;
+}
+
+
+IngListViewItem::IngListViewItem( QListView* qlv, const Ingredient &i ) : QListViewItem( qlv )
+{
+ init( i );
+}
+
+IngListViewItem::IngListViewItem( QListView* qlv, QListViewItem *after, const Ingredient &i ) : QListViewItem( qlv, after )
+{
+ init( i );
+}
+
+IngListViewItem::IngListViewItem( QListViewItem* qli, QListViewItem *after, const Ingredient &i ) : QListViewItem( qli, after )
+{
+ init( i );
+}
+
+int IngListViewItem::rtti() const
+{
+ return INGLISTVIEWITEM_RTTI;
+}
+
+Ingredient IngListViewItem::ingredient() const
+{
+ return m_ing;
+}
+
+void IngListViewItem::setAmount( double amount, double amount_offset )
+{
+ amount_str = QString::null;
+
+ if ( amount > 0 ) {
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Formatting" );
+
+ if ( config->readBoolEntry( "Fraction" ) )
+ amount_str = MixedNumber( amount ).toString();
+ else
+ amount_str = beautify( KGlobal::locale() ->formatNumber( amount, 5 ) );
+ }
+ if ( amount_offset > 0 ) {
+ if ( amount < 1e-10 )
+ amount_str += "0";
+ amount_str += "-" + MixedNumber(amount+amount_offset).toString();
+ }
+
+ m_ing.amount = amount;
+ m_ing.amount_offset = amount_offset;
+
+ //FIXME: make sure the right unit is showing after changing this (force a repaint... repaint() doesn't do the job right because it gets caught in a loop)
+}
+
+void IngListViewItem::setUnit( const Unit &unit )
+{
+ //### This shouldn't be necessary... the db backend should ensure this doesn't happen
+ if ( !unit.name.isEmpty() )
+ m_ing.units.name = unit.name;
+ if ( !unit.plural.isEmpty() )
+ m_ing.units.plural = unit.plural;
+}
+
+void IngListViewItem::setPrepMethod( const QString &prepMethod )
+{
+ m_ing.prepMethodList = ElementList::split(",",prepMethod);
+}
+
+void IngListViewItem::setText( int column, const QString &text )
+{
+ switch ( column ) {
+ case 0: {
+ m_ing.name = text;
+ break;
+ }
+ case 1: {
+ Ingredient i; i.setAmount(text);
+ setAmount( i.amount, i.amount_offset );
+ break;
+ }
+ case 2:
+ setUnit( Unit( text, m_ing.amount+m_ing.amount_offset ) );
+ break;
+ case 3:
+ setPrepMethod( text );
+ break;
+ default:
+ break;
+ }
+}
+
+QString IngListViewItem::text( int column ) const
+{
+ switch ( column ) {
+ case 0:
+ return m_ing.name;
+ break;
+ case 1:
+ return amount_str;
+ break;
+ case 2:
+ return ( m_ing.amount+m_ing.amount_offset > 1 ) ? m_ing.units.plural : m_ing.units.name;
+ break;
+ case 3:
+ return m_ing.prepMethodList.join(",");
+ break;
+ default:
+ return ( QString::null );
+ }
+}
+
+void IngListViewItem::init( const Ingredient &i )
+{
+ m_ing = i;
+
+ setAmount( i.amount, i.amount_offset );
+}
+
+
+IngGrpListViewItem::IngGrpListViewItem( QListView* qlv, QListViewItem *after, const QString &group, int id ) : QListViewItem( qlv, after )
+{
+ init( group, id );
+}
+
+int IngGrpListViewItem::rtti() const
+{
+ return INGGRPLISTVIEWITEM_RTTI;
+}
+
+QString IngGrpListViewItem::group() const
+{
+ return m_group;
+}
+
+int IngGrpListViewItem::id() const
+{
+ return m_id;
+}
+
+QString IngGrpListViewItem::text( int column ) const
+{
+ switch ( column ) {
+ case 0:
+ return m_group + ":";
+ break;
+ default:
+ return ( QString::null );
+ }
+}
+
+void IngGrpListViewItem::setText( int column, const QString &text )
+{
+ switch ( column ) {
+ case 0:
+ if ( text.right(1) == ":" )
+ m_group = text.left(text.length()-1);
+ else
+ m_group = text;
+ break;
+ default:
+ break;
+ }
+}
+
+void IngGrpListViewItem::init( const QString &group, int id )
+{
+ m_group = group;
+ m_id = id;
+}
+
diff --git a/krecipes/src/widgets/inglistviewitem.h b/krecipes/src/widgets/inglistviewitem.h
new file mode 100644
index 0000000..a870a5a
--- /dev/null
+++ b/krecipes/src/widgets/inglistviewitem.h
@@ -0,0 +1,82 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGLISTVIEWITEM_H
+#define INGLISTVIEWITEM_H
+
+#include "qlistview.h"
+
+#include "datablocks/ingredient.h"
+
+#define INGGRPLISTVIEWITEM_RTTI 1003
+#define INGLISTVIEWITEM_RTTI 1004
+#define INGSUBLISTVIEWITEM_RTTI 1009
+
+class IngListViewItem : public QListViewItem
+{
+public:
+ IngListViewItem( QListView* qlv, const Ingredient &i );
+ IngListViewItem( QListView* qlv, QListViewItem *after, const Ingredient &i );
+ IngListViewItem( QListViewItem* qli, QListViewItem *after, const Ingredient &i );
+
+ int rtti() const;
+
+ Ingredient ingredient() const;
+
+ void setAmount( double amount, double amount_offset );
+ void setUnit( const Unit &unit );
+ void setPrepMethod( const QString &prepMethod );
+
+protected:
+ Ingredient m_ing;
+ QString amount_str;
+
+public:
+ virtual QString text( int column ) const;
+ virtual void setText( int column, const QString &text );
+
+private:
+ void init( const Ingredient &i );
+};
+
+
+class IngSubListViewItem : public IngListViewItem
+{
+public:
+ IngSubListViewItem( QListViewItem* qli, const Ingredient &i );
+
+ virtual QString text( int column ) const;
+ virtual void setText( int column, const QString &text );
+ virtual int rtti() const;
+};
+
+
+class IngGrpListViewItem : public QListViewItem
+{
+public:
+ IngGrpListViewItem( QListView* qlv, QListViewItem *after, const QString &group, int id );
+
+ int rtti() const;
+
+ QString group() const;
+ int id() const;
+
+ virtual QString text( int column ) const;
+ virtual void setText( int column, const QString &text );
+
+protected:
+ QString m_group;
+ int m_id;
+
+private:
+ void init( const QString &group, int id );
+};
+
+#endif
diff --git a/krecipes/src/widgets/ingredientcombobox.cpp b/krecipes/src/widgets/ingredientcombobox.cpp
new file mode 100644
index 0000000..7423f22
--- /dev/null
+++ b/krecipes/src/widgets/ingredientcombobox.cpp
@@ -0,0 +1,188 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientcombobox.h"
+
+#include <qlistbox.h>
+#include <qtimer.h>
+
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kglobal.h>
+#include <kconfig.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/elementlist.h"
+
+IngredientComboBox::IngredientComboBox( bool b, QWidget *parent, RecipeDB *db, const QString &specialItem ) : KComboBox( b, parent ),
+ database( db ), loading_at(0), load_timer(new QTimer(this)), m_specialItem(specialItem)
+{
+ connect( load_timer, SIGNAL(timeout()), SLOT(loadMore()) );
+ completionObject()->setIgnoreCase(true);
+}
+
+void IngredientComboBox::reload()
+{
+ QString remember_text;
+ if ( editable() )
+ remember_text = lineEdit()->text();
+
+ ElementList ingredientList;
+ database->loadIngredients( &ingredientList );
+
+ clear();
+ ingredientComboRows.clear();
+
+ int row = 0;
+ if ( !m_specialItem.isNull() ) {
+ insertItem(m_specialItem);
+ ingredientComboRows.insert( row, -1 );
+ row++;
+ }
+ for ( ElementList::const_iterator it = ingredientList.begin(); it != ingredientList.end(); ++it, ++row ) {
+ insertItem((*it).name);
+ completionObject()->addItem((*it).name);
+ ingredientComboRows.insert( row, (*it).id );
+ }
+
+ if ( editable() )
+ setEditText( remember_text );
+
+ database->disconnect( this );
+ connect( database, SIGNAL( ingredientCreated( const Element & ) ), SLOT( createIngredient( const Element & ) ) );
+ connect( database, SIGNAL( ingredientRemoved( int ) ), SLOT( removeIngredient( int ) ) );
+}
+
+void IngredientComboBox::loadMore()
+{
+ if ( loading_at >= ing_count-1 ) {
+ endLoad();
+ return;
+ }
+
+ ElementList ingredientList;
+ database->loadIngredients( &ingredientList, load_limit, loading_at );
+
+ for ( ElementList::const_iterator it = ingredientList.begin(); it != ingredientList.end(); ++it, ++loading_at ) {
+ insertItem((*it).name);
+ completionObject()->addItem((*it).name);
+ ingredientComboRows.insert( loading_at, (*it).id );
+ }
+}
+
+void IngredientComboBox::startLoad()
+{
+ //don't receive ingredient created/removed events from the database
+ database->disconnect( this );
+
+ KConfig * config = KGlobal::config(); config->setGroup( "Performance" );
+ load_limit = config->readNumEntry( "Limit", -1 );
+ if ( load_limit == -1 ) {
+ reload();
+ endLoad();
+ }
+ else {
+ loading_at = 0;
+ ing_count = database->ingredientCount();
+
+ load_timer->start( 0, false );
+ }
+}
+
+void IngredientComboBox::endLoad()
+{
+ load_timer->stop();
+
+ //now we're ready to receive ingredient created/removed events from the database
+ connect( database, SIGNAL( ingredientCreated( const Element & ) ), SLOT( createIngredient( const Element & ) ) );
+ connect( database, SIGNAL( ingredientRemoved( int ) ), SLOT( removeIngredient( int ) ) );
+}
+
+int IngredientComboBox::id( int row )
+{
+ return ingredientComboRows[ row ];
+}
+
+int IngredientComboBox::id( const QString &ing )
+{
+ for ( int i = 0; i < count(); i++ ) {
+ if ( ing == text( i ) )
+ return id(i);
+ }
+ kdDebug()<<"Warning: couldn't find the ID for "<<ing<<endl;
+ return -1;
+}
+
+void IngredientComboBox::createIngredient( const Element &element )
+{
+ int row = findInsertionPoint( element.name );
+
+ QString remember_text;
+ if ( editable() )
+ remember_text = lineEdit()->text();
+
+ insertItem( element.name, row );
+ completionObject()->addItem(element.name);
+
+ if ( editable() )
+ lineEdit()->setText( remember_text );
+
+ //now update the map by pushing everything after this item down
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = ingredientComboRows.begin(); it != ingredientComboRows.end(); ++it ) {
+ if ( it.key() >= row ) {
+ new_map.insert( it.key() + 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ ingredientComboRows = new_map;
+ ingredientComboRows.insert( row, element.id );
+}
+
+void IngredientComboBox::removeIngredient( int id )
+{
+ int row = -1;
+ for ( QMap<int, int>::iterator it = ingredientComboRows.begin(); it != ingredientComboRows.end(); ++it ) {
+ if ( it.data() == id ) {
+ row = it.key();
+ completionObject()->removeItem( text(row) );
+ removeItem( row );
+ ingredientComboRows.remove( it );
+ break;
+ }
+ }
+
+ if ( row == -1 )
+ return ;
+
+ //now update the map by pushing everything after this item up
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = ingredientComboRows.begin(); it != ingredientComboRows.end(); ++it ) {
+ if ( it.key() > row ) {
+ new_map.insert( it.key() - 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ ingredientComboRows = new_map;
+}
+
+int IngredientComboBox::findInsertionPoint( const QString &name )
+{
+ for ( int i = 0; i < count(); i++ ) {
+ if ( QString::localeAwareCompare( name, text( i ) ) < 0 )
+ return i;
+ }
+
+ return count();
+}
+
+#include "ingredientcombobox.moc"
diff --git a/krecipes/src/widgets/ingredientcombobox.h b/krecipes/src/widgets/ingredientcombobox.h
new file mode 100644
index 0000000..8e18957
--- /dev/null
+++ b/krecipes/src/widgets/ingredientcombobox.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTCOMBOBOX_H
+#define INGREDIENTCOMBOBOX_H
+
+#include <kcombobox.h>
+
+#include <qmap.h>
+
+#include "datablocks/element.h"
+
+class QTimer;
+
+class RecipeDB;
+class ElementList;
+
+class IngredientComboBox : public KComboBox
+{
+ Q_OBJECT
+
+public:
+ IngredientComboBox( bool, QWidget *parent, RecipeDB *db, const QString &specialItem = QString::null );
+
+ void reload();
+ int id( int row );
+ int id( const QString &ing );
+
+ void startLoad();
+ void endLoad();
+
+private slots:
+ void createIngredient( const Element &element );
+ void removeIngredient( int id );
+
+ int findInsertionPoint( const QString &name );
+ void loadMore();
+
+private:
+ RecipeDB *database;
+ QMap<int, int> ingredientComboRows; // Contains the category id for every given row in the category combobox
+
+ int loading_at;
+ int ing_count;
+ int load_limit;
+ QTimer *load_timer;
+ QString m_specialItem;
+};
+
+#endif //INGREDIENTCOMBOBOX_H
+
diff --git a/krecipes/src/widgets/ingredientinputwidget.cpp b/krecipes/src/widgets/ingredientinputwidget.cpp
new file mode 100644
index 0000000..ef7975f
--- /dev/null
+++ b/krecipes/src/widgets/ingredientinputwidget.cpp
@@ -0,0 +1,542 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientinputwidget.h"
+
+#include <qlabel.h>
+#include <qwidgetstack.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qgroupbox.h>
+#include <qbuttongroup.h>
+#include <qradiobutton.h>
+#include <qcheckbox.h>
+
+#include <kcombobox.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/unit.h"
+#include "widgets/fractioninput.h"
+#include "widgets/ingredientcombobox.h"
+#include "widgets/headercombobox.h"
+#include "widgets/prepmethodcombobox.h"
+#include "dialogs/createunitdialog.h"
+
+#include "profiling.h"
+
+IngredientInput::IngredientInput( RecipeDB *db, QWidget *parent, bool allowHeader ) : QHBox(parent), database(db)
+{
+ QVBox *ingredientVBox = new QVBox( this );
+ QHBox *typeHBox = new QHBox( ingredientVBox );
+
+ if ( allowHeader ) {
+ typeButtonGrp = new QButtonGroup();
+ QRadioButton *ingredientRadioButton = new QRadioButton( i18n( "Ingredient:" ), typeHBox );
+ typeButtonGrp->insert( ingredientRadioButton, 0 );
+
+ QRadioButton *headerRadioButton = new QRadioButton( i18n( "Ingredient grouping name", "Header:" ), typeHBox );
+ typeButtonGrp->insert( headerRadioButton, 1 );
+
+ typeButtonGrp->setButton( 0 );
+ connect( typeButtonGrp, SIGNAL( clicked( int ) ), SLOT( typeButtonClicked( int ) ) );
+ }
+ else {
+ (void) new QLabel( i18n( "Ingredient:" ), typeHBox );
+ typeButtonGrp = 0;
+ }
+
+ header_ing_stack = new QWidgetStack(ingredientVBox);
+ ingredientBox = new IngredientComboBox( TRUE, header_ing_stack, database );
+ ingredientBox->setAutoCompletion( TRUE );
+ ingredientBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box
+ ingredientBox->setSizePolicy( QSizePolicy( QSizePolicy::Ignored, QSizePolicy::Fixed ) );
+ header_ing_stack->addWidget( ingredientBox );
+ headerBox = new HeaderComboBox( TRUE, header_ing_stack, database );
+ headerBox->setAutoCompletion( TRUE );
+ headerBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box
+ headerBox->setSizePolicy( QSizePolicy( QSizePolicy::Ignored, QSizePolicy::Fixed ) );
+ header_ing_stack->addWidget( headerBox );
+
+ QVBox *amountVBox = new QVBox( this );
+ amountLabel = new QLabel( i18n( "Amount:" ), amountVBox );
+ amountEdit = new FractionInput( amountVBox );
+ amountEdit->setAllowRange(true);
+ amountEdit->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ) );
+
+ QVBox *unitVBox = new QVBox( this );
+ unitLabel = new QLabel( i18n( "Unit:" ), unitVBox );
+ unitBox = new KComboBox( TRUE, unitVBox );
+ unitBox->setAutoCompletion( TRUE );
+ unitBox->lineEdit() ->disconnect( unitBox ); //so hitting enter doesn't enter the item into the box
+ unitBox->setSizePolicy( QSizePolicy( QSizePolicy::Ignored, QSizePolicy::Fixed ) );
+
+ QVBox *prepMethodVBox = new QVBox( this );
+ prepMethodLabel = new QLabel( i18n( "Preparation Method:" ), prepMethodVBox );
+ prepMethodBox = new PrepMethodComboBox( TRUE, prepMethodVBox, database );
+ prepMethodBox->setAutoCompletion( TRUE );
+ prepMethodBox->lineEdit() ->disconnect( prepMethodBox ); //so hitting enter doesn't enter the item into the box
+ prepMethodBox->setSizePolicy( QSizePolicy( QSizePolicy::Ignored, QSizePolicy::Fixed ) );
+
+ orButton = new QCheckBox( i18n( "OR" ), this );
+
+ setStretchFactor( ingredientVBox, 5 );
+ setStretchFactor( amountVBox, 1 );
+ setStretchFactor( unitVBox, 2 );
+ setStretchFactor( prepMethodVBox, 3 );
+
+ connect( ingredientBox, SIGNAL( activated( int ) ), this, SLOT( loadUnitListCombo() ) );
+ connect( ingredientBox->lineEdit(), SIGNAL( lostFocus() ), this, SLOT( slotIngredientBoxLostFocus() ) );
+ connect( unitBox->lineEdit(), SIGNAL( lostFocus() ), this, SLOT( slotUnitBoxLostFocus() ) );
+ connect( prepMethodBox->lineEdit(), SIGNAL( lostFocus() ), this, SLOT( slotPrepMethodBoxLostFocus() ) );
+ connect( orButton, SIGNAL( toggled(bool) ), this, SLOT( orToggled(bool) ) );
+
+ connect( unitBox->lineEdit(), SIGNAL( returnPressed() ), this, SLOT( signalIngredient() ) );
+ connect( ingredientBox->lineEdit(), SIGNAL( returnPressed() ), this, SLOT( signalIngredient() ) );
+ connect( headerBox->lineEdit(), SIGNAL( returnPressed() ), this, SLOT( signalIngredient() ) );
+ connect( prepMethodBox->lineEdit(), SIGNAL( returnPressed() ), this, SLOT( signalIngredient() ) );
+ connect( amountEdit, SIGNAL( returnPressed( const QString & ) ), this, SLOT( signalIngredient() ) );
+
+ unitComboList = new UnitList;
+
+ setFocusProxy( ingredientBox );
+}
+
+IngredientInput::~IngredientInput()
+{
+ delete unitComboList;
+ delete typeButtonGrp;
+}
+
+void IngredientInput::clear()
+{
+ unitComboList->clear();
+
+ orButton->setChecked(false);
+ typeButtonGrp->setButton( 0 ); //put back to ingredient input
+ typeButtonClicked( 0 );
+
+ amountEdit->clear();
+ ingredientBox->lineEdit()->setText("");
+ prepMethodBox->lineEdit()->setText("");
+ headerBox->lineEdit()->setText("");
+ unitBox->lineEdit()->setText("");
+}
+
+void IngredientInput::orToggled(bool b)
+{
+ emit orToggled(b,this);
+}
+
+void IngredientInput::reloadCombos()
+{
+ //these only needed to be loaded once
+ if ( ingredientBox->count() == 0 ) {
+ START_TIMER("Loading ingredient input auto-completion");
+ ingredientBox->reload();
+ END_TIMER();
+ }
+ if ( headerBox->count() == 0 ) {
+ START_TIMER("Loading ingredient header input auto-completion");
+ headerBox->reload();
+ END_TIMER();
+ }
+ if ( prepMethodBox->count() == 0 ) {
+ START_TIMER("Loading prep method input auto-completion");
+ prepMethodBox->reload();
+ END_TIMER();
+ }
+
+ loadUnitListCombo();
+}
+
+void IngredientInput::slotIngredientBoxLostFocus( void )
+{
+ if ( ingredientBox->contains( ingredientBox->currentText() ) ) {
+ ingredientBox->setCurrentItem( ingredientBox->currentText() );
+ loadUnitListCombo();
+ }
+ else {
+ unitBox->clear();
+ unitBox->completionObject() ->clear();
+ unitComboList->clear();
+ }
+}
+
+void IngredientInput::slotUnitBoxLostFocus()
+{
+ if ( unitBox->contains( unitBox->currentText() ) )
+ unitBox->setCurrentItem( unitBox->currentText() );
+}
+
+void IngredientInput::slotPrepMethodBoxLostFocus()
+{
+ if ( prepMethodBox->contains( prepMethodBox->currentText() ) )
+ prepMethodBox->setCurrentItem( prepMethodBox->currentText() );
+}
+
+void IngredientInput::typeButtonClicked( int button_id )
+{
+ if ( amountEdit->isEnabled() == !bool( button_id ) ) //it is already set (the same button was clicked more than once)
+ return ;
+
+ amountEdit->setEnabled( !bool( button_id ) );
+ unitBox->setEnabled( !bool( button_id ) );
+ prepMethodBox->setEnabled( !bool( button_id ) );
+
+ if ( button_id == 1 ) { //Header
+ header_ing_stack->raiseWidget( headerBox );
+ }
+ else {
+ header_ing_stack->raiseWidget( ingredientBox );
+ }
+}
+
+void IngredientInput::enableHeader( bool enable )
+{
+ if ( !enable ) {
+ typeButtonGrp->setButton( 0 ); //put back to ingredient input
+ typeButtonClicked( 0 );
+ }
+ typeButtonGrp->find(1)->setEnabled(enable);
+}
+
+void IngredientInput::signalIngredient()
+{
+ //validate input; if successful, emit signal
+ if ( isHeader() ) {
+ if ( header().isEmpty() )
+ return;
+ }
+ else {
+ if ( !isInputValid() )
+ return;
+ }
+
+ emit addIngredient();
+}
+
+bool IngredientInput::isInputValid()
+{
+ if ( ingredientBox->currentText().stripWhiteSpace().isEmpty() ) {
+ KMessageBox::error( this, i18n( "Please enter an ingredient" ), QString::null );
+ ingredientBox->setFocus();
+ return false;
+ }
+ return checkAmountEdit() && checkBounds();
+}
+
+bool IngredientInput::checkBounds()
+{
+ if ( ingredientBox->currentText().length() > uint(database->maxIngredientNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Ingredient name cannot be longer than %1 characters." ) ).arg( database->maxIngredientNameLength() ) );
+ ingredientBox->lineEdit() ->setFocus();
+ ingredientBox->lineEdit() ->selectAll();
+ return false;
+ }
+
+ if ( unitBox->currentText().length() > uint(database->maxUnitNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) );
+ unitBox->lineEdit() ->setFocus();
+ unitBox->lineEdit() ->selectAll();
+ return false;
+ }
+
+ QStringList prepMethodList = QStringList::split(",",prepMethodBox->currentText());
+ for ( QStringList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it ) {
+ if ( (*it).stripWhiteSpace().length() > uint(database->maxPrepMethodNameLength()) )
+ {
+ KMessageBox::error( this, QString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) );
+ prepMethodBox->lineEdit() ->setFocus();
+ prepMethodBox->lineEdit() ->selectAll();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IngredientInput::checkAmountEdit()
+{
+ if ( amountEdit->isInputValid() )
+ return true;
+ else {
+ KMessageBox::error( this, i18n( "Amount field contains invalid input." ),
+ i18n( "Invalid input" ) );
+ amountEdit->setFocus();
+ amountEdit->selectAll();
+ return false;
+ }
+}
+
+void IngredientInput::loadUnitListCombo()
+{
+ QString store_unit = unitBox->currentText();
+ unitBox->clear(); // Empty the combo first
+ unitBox->completionObject() ->clear();
+
+ int comboIndex = ingredientBox->currentItem();
+ int comboCount = ingredientBox->count();
+
+ if ( comboCount > 0 ) { // If not, the list may be empty (no ingredient list defined) and crashes while reading
+ int selectedIngredient = ingredientBox->id( comboIndex );
+ database->loadPossibleUnits( selectedIngredient, unitComboList );
+
+ //Populate this data into the ComboBox
+ for ( UnitList::const_iterator unit_it = unitComboList->begin(); unit_it != unitComboList->end(); ++unit_it ) {
+ unitBox->insertItem( ( *unit_it ).name );
+ unitBox->completionObject() ->addItem( ( *unit_it ).name );
+ if ( ( *unit_it ).name != (*unit_it ).plural ) {
+ unitBox->insertItem( ( *unit_it ).plural );
+ unitBox->completionObject() ->addItem( ( *unit_it ).plural );
+ }
+
+ if ( !( *unit_it ).name_abbrev.isEmpty() ) {
+ unitBox->insertItem( ( *unit_it ).name_abbrev );
+ unitBox->completionObject() ->addItem( ( *unit_it ).name_abbrev );
+ }
+ if ( !(*unit_it ).plural_abbrev.isEmpty() &&
+ ( *unit_it ).name_abbrev != (*unit_it ).plural_abbrev ) {
+ unitBox->insertItem( ( *unit_it ).plural_abbrev );
+ unitBox->completionObject() ->addItem( ( *unit_it ).plural_abbrev );
+ }
+
+ }
+ }
+ unitBox->lineEdit() ->setText( store_unit );
+}
+
+bool IngredientInput::isHeader() const
+{
+ return typeButtonGrp && (typeButtonGrp->id( typeButtonGrp->selected() ) == 1);
+}
+
+Ingredient IngredientInput::ingredient() const
+{
+ Ingredient ing;
+
+ ing.prepMethodList = ElementList::split(",",prepMethodBox->currentText());
+ ing.name = ingredientBox->currentText();
+ amountEdit->value(ing.amount,ing.amount_offset);
+ ing.units = Unit(unitBox->currentText().stripWhiteSpace(),ing.amount+ing.amount_offset);
+ ing.ingredientID = ingredientBox->id( ingredientBox->currentItem() );
+
+ return ing;
+}
+
+QString IngredientInput::header() const
+{
+ return headerBox->currentText().stripWhiteSpace();
+}
+
+void IngredientInput::updateTabOrder()
+{
+ QWidget::setTabOrder( ingredientBox, amountEdit );
+ QWidget::setTabOrder( amountEdit, unitBox );
+ QWidget::setTabOrder( unitBox, prepMethodBox );
+ QWidget::setTabOrder( prepMethodBox, orButton );
+}
+
+
+IngredientInputWidget::IngredientInputWidget( RecipeDB *db, QWidget *parent ) : QVBox(parent), database(db)
+{
+ setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) );
+
+ m_ingInputs.append(new IngredientInput(database,this));
+
+ // Connect signals & Slots
+ connect( m_ingInputs[0], SIGNAL(addIngredient()), this, SLOT(addIngredient()) );
+ connect( m_ingInputs[0], SIGNAL(orToggled(bool,IngredientInput*)), this, SLOT(updateInputs(bool,IngredientInput*)) );
+
+ reloadCombos();
+}
+
+IngredientInputWidget::~IngredientInputWidget()
+{
+}
+
+void IngredientInputWidget::clear()
+{
+ //clearing the first input deletes all substitute inputs
+ m_ingInputs[0]->clear();
+}
+
+void IngredientInputWidget::updateInputs(bool on, IngredientInput* input)
+{
+ QValueList<IngredientInput*>::iterator curr = m_ingInputs.find(input);
+ IngredientInput *prev_input = *curr;
+ ++curr;
+
+ if ( on ) {
+ IngredientInput *new_input = new IngredientInput(database,this,false);
+ new_input->reloadCombos();
+
+ QWidget::setTabOrder( prev_input, new_input );
+ new_input->updateTabOrder();
+
+ connect( new_input, SIGNAL(addIngredient()), this, SLOT(addIngredient()) );
+ connect( new_input, SIGNAL(orToggled(bool,IngredientInput*)), this, SLOT(updateInputs(bool,IngredientInput*)) );
+
+ new_input->show();
+ m_ingInputs.insert(curr,new_input);
+
+ m_ingInputs[0]->enableHeader(false);
+
+ }
+ else {
+ while ( curr != m_ingInputs.end() ) {
+ (*curr)->deleteLater();
+ curr = m_ingInputs.remove(curr);
+ }
+ if ( m_ingInputs.count() == 1 )
+ m_ingInputs[0]->enableHeader(true);
+ }
+}
+
+void IngredientInputWidget::addIngredient()
+{
+ if ( m_ingInputs[0]->isHeader() ) {
+ QString header = m_ingInputs[0]->header();
+ if ( header.isEmpty() )
+ return;
+
+ int group_id = createNewGroupIfNecessary( header,database );
+ emit headerEntered( Element(header,group_id) );
+ }
+ else {
+ for ( QValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) {
+ if ( !(*it)->isInputValid() )
+ return;
+ }
+
+ QValueList<IngredientData> list;
+ for ( QValueList<IngredientInput*>::const_iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) {
+ Ingredient ing = (*it)->ingredient();
+ ing.ingredientID = createNewIngredientIfNecessary(ing.name,database);
+
+ bool plural = ing.amount+ing.amount_offset > 1;
+ ing.units.id = createNewUnitIfNecessary( (plural)?ing.units.plural:ing.units.name, plural, ing.ingredientID, ing.units,database );
+ if ( ing.units.id == -1 ) // this will happen if the dialog to create a unit was cancelled
+ return ;
+
+ QValueList<int> prepIDs = createNewPrepIfNecessary( ing.prepMethodList,database );
+ QValueList<int>::const_iterator id_it = prepIDs.begin();
+ for ( ElementList::iterator it = ing.prepMethodList.begin(); it != ing.prepMethodList.end(); ++it, ++id_it ) {
+ (*it).id = *id_it;
+ }
+
+ list.append(ing);
+ }
+
+ Ingredient ing = list.first();
+ list.pop_front();
+ ing.substitutes = list;
+ emit ingredientEntered( ing );
+ }
+ clear();
+
+ m_ingInputs[0]->setFocus(); //put cursor back to the ingredient name so user can begin next ingredient
+}
+
+int IngredientInputWidget::createNewIngredientIfNecessary( const QString &ing, RecipeDB *database )
+{
+ int id = -1;
+ if ( ing.isEmpty() )
+ return -1;
+
+ id = database->findExistingIngredientByName( ing );
+ if ( id == -1 ) {
+ database->createNewIngredient( ing );
+ id = database->lastInsertID();
+ }
+ return id;
+}
+
+int IngredientInputWidget::createNewUnitIfNecessary( const QString &unit, bool plural, int ingredientID, Unit &new_unit, RecipeDB *database )
+{
+ int id = database->findExistingUnitByName( unit );
+ if ( -1 == id )
+ {
+ CreateUnitDialog getUnit( 0, ( plural ) ? QString::null : unit, ( !plural ) ? QString::null : unit );
+ if ( getUnit.exec() == QDialog::Accepted ) {
+ new_unit = getUnit.newUnit();
+ database->createNewUnit( new_unit );
+
+ id = database->lastInsertID();
+ }
+ }
+
+ if ( !database->ingredientContainsUnit(
+ ingredientID,
+ id ) )
+ {
+ database->addUnitToIngredient(
+ ingredientID,
+ id );
+ }
+
+ new_unit = database->unitName( id );
+
+ //loadUnitListCombo();
+ return id;
+}
+
+QValueList<int> IngredientInputWidget::createNewPrepIfNecessary( const ElementList &prepMethods, RecipeDB *database )
+{
+ QValueList<int> ids;
+
+ if ( prepMethods.isEmpty() ) //no prep methods
+ return ids;
+ else
+ {
+ for ( ElementList::const_iterator it = prepMethods.begin(); it != prepMethods.end(); ++it ) {
+ int id = database->findExistingPrepByName( (*it).name.stripWhiteSpace() );
+ if ( id == -1 )
+ {
+ database->createNewPrepMethod( (*it).name.stripWhiteSpace() );
+ id = database->lastInsertID();
+ }
+ ids << id;
+ }
+
+ return ids;
+ }
+}
+
+int IngredientInputWidget::createNewGroupIfNecessary( const QString &group, RecipeDB *database )
+{
+ if ( group.stripWhiteSpace().isEmpty() ) //no group
+ return -1;
+ else
+ {
+ int id = database->findExistingIngredientGroupByName( group );
+ if ( id == -1 ) //creating new
+ {
+ database->createNewIngGroup( group );
+ id = database->lastInsertID();
+ }
+
+ return id;
+ }
+}
+
+void IngredientInputWidget::reloadCombos()
+{
+ for ( QValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it )
+ (*it)->reloadCombos();
+}
+
+#include "ingredientinputwidget.moc"
diff --git a/krecipes/src/widgets/ingredientinputwidget.h b/krecipes/src/widgets/ingredientinputwidget.h
new file mode 100644
index 0000000..8a5f8f7
--- /dev/null
+++ b/krecipes/src/widgets/ingredientinputwidget.h
@@ -0,0 +1,134 @@
+/***************************************************************************
+* Copyright (C) 2003-2005 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTINPUTWIDGET_H
+#define INGREDIENTINPUTWIDGET_H
+
+#include <qvbox.h>
+
+#include "datablocks/unit.h"
+
+class QVBox;
+class QHBox;
+class QLabel;
+class QButtonGroup;
+class QWidgetStack;
+class QGroupBox;
+class QRadioButton;
+class QCheckBox;
+
+class KComboBox;
+
+class IngredientComboBox;
+class HeaderComboBox;
+class PrepMethodComboBox;
+class RecipeDB;
+class FractionInput;
+class Ingredient;
+class Element;
+class ElementList;
+class IngredientInput;
+
+class IngredientInputWidget : public QVBox
+{
+Q_OBJECT
+
+public:
+ IngredientInputWidget( RecipeDB *db, QWidget *parent );
+ ~IngredientInputWidget();
+
+ void clear();
+
+ static int createNewIngredientIfNecessary( const QString &ing, RecipeDB *db );
+ static int createNewUnitIfNecessary( const QString &unit, bool plural, int ingredient_id, Unit &new_unit, RecipeDB *db );
+ static QValueList<int> createNewPrepIfNecessary( const ElementList &prepMethods, RecipeDB *db );
+ static int createNewGroupIfNecessary( const QString &group, RecipeDB *db );
+
+signals:
+ void ingredientEntered( const Ingredient &ing );
+
+ void headerEntered( const Element &header );
+
+public slots:
+ void addIngredient();
+
+private slots:
+ void updateInputs(bool,IngredientInput*);
+
+private:
+ /** Reloads lists of units, ingredients, and preparation methods */
+ void reloadCombos();
+
+ void checkIfNewUnits();
+
+ RecipeDB *database;
+
+ QValueList<IngredientInput*> m_ingInputs;
+};
+
+class IngredientInput : public QHBox
+{
+Q_OBJECT
+
+public:
+ IngredientInput( RecipeDB *db, QWidget *parent, bool allowHeader = true );
+ ~IngredientInput();
+
+ void clear();
+ bool isInputValid();
+
+ bool isHeader() const;
+ Ingredient ingredient() const;
+ QString header() const;
+
+ void reloadCombos();
+ void enableHeader( bool );
+ void updateTabOrder();
+
+signals:
+ void addIngredient();
+ void orToggled(bool,IngredientInput*);
+
+private slots:
+ void loadUnitListCombo();
+ void signalIngredient();
+ void typeButtonClicked( int );
+ void slotIngredientBoxLostFocus();
+ void slotUnitBoxLostFocus();
+ void slotPrepMethodBoxLostFocus();
+ void orToggled(bool);
+
+private:
+ bool checkBounds();
+ bool checkAmountEdit();
+
+ RecipeDB *database;
+ UnitList *unitComboList;
+
+ QCheckBox *orButton;
+ QGroupBox *ingredientGBox;
+ QLabel *amountLabel;
+ FractionInput* amountEdit;
+ QLabel *unitLabel;
+ KComboBox* unitBox;
+ QLabel *prepMethodLabel;
+ PrepMethodComboBox* prepMethodBox;
+ QLabel *ingredientLabel;
+ IngredientComboBox* ingredientBox;
+ HeaderComboBox* headerBox;
+ QWidgetStack *header_ing_stack;
+ QButtonGroup *typeButtonGrp;
+};
+
+#endif //INGREDIENTINPUTWIDGET_H
diff --git a/krecipes/src/widgets/ingredientlistview.cpp b/krecipes/src/widgets/ingredientlistview.cpp
new file mode 100644
index 0000000..887896d
--- /dev/null
+++ b/krecipes/src/widgets/ingredientlistview.cpp
@@ -0,0 +1,287 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "ingredientlistview.h"
+
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include "backends/recipedb.h"
+#include "dialogs/createelementdialog.h"
+#include "dialogs/dependanciesdialog.h"
+
+IngredientCheckListItem::IngredientCheckListItem( IngredientCheckListView* qlv, const Element &ing ) : QCheckListItem( qlv, QString::null, QCheckListItem::CheckBox ),
+ m_listview(qlv)
+{
+ // Initialize the ingredient data with the the property data
+ ingStored = new Element();
+ ingStored->id = ing.id;
+ ingStored->name = ing.name;
+}
+
+IngredientCheckListItem::IngredientCheckListItem( IngredientCheckListView* qlv, QListViewItem *after, const Element &ing ) : QCheckListItem( qlv, after, QString::null, QCheckListItem::CheckBox ),
+ m_listview(qlv)
+{
+ // Initialize the ingredient data with the the property data
+ ingStored = new Element();
+ ingStored->id = ing.id;
+ ingStored->name = ing.name;
+}
+
+IngredientCheckListItem::~IngredientCheckListItem( void )
+{
+ delete ingStored;
+}
+int IngredientCheckListItem::id( void ) const
+{
+ return ingStored->id;
+}
+QString IngredientCheckListItem::name( void ) const
+{
+ return ingStored->name;
+}
+Element IngredientCheckListItem::ingredient() const
+{
+ return *ingStored;
+}
+
+QString IngredientCheckListItem::text( int column ) const
+{
+ switch ( column ) {
+ case 0:
+ return ( ingStored->name );
+ case 1:
+ return ( QString::number( ingStored->id ) );
+ default:
+ return QString::null;
+ }
+}
+
+void IngredientCheckListItem::stateChange( bool on )
+{
+ m_listview->stateChange(this,on);
+}
+
+IngredientListView::IngredientListView( QWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db, db->ingredientCount() )
+{
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+}
+
+void IngredientListView::init()
+{
+ connect( database, SIGNAL( ingredientCreated( const Element & ) ), SLOT( checkCreateIngredient( const Element & ) ) );
+ connect( database, SIGNAL( ingredientRemoved( int ) ), SLOT( removeIngredient( int ) ) );
+}
+
+void IngredientListView::load( int limit, int offset )
+{
+ ElementList ingredientList;
+ database->loadIngredients( &ingredientList, limit, offset );
+
+ setTotalItems(ingredientList.count());
+
+ for ( ElementList::const_iterator ing_it = ingredientList.begin(); ing_it != ingredientList.end(); ++ing_it )
+ createIngredient( *ing_it );
+}
+
+void IngredientListView::checkCreateIngredient( const Element &el )
+{
+ if ( handleElement(el.name) ) { //only create this ingredient if the base class okays it
+ createIngredient(el);
+ }
+}
+
+
+StdIngredientListView::StdIngredientListView( QWidget *parent, RecipeDB *db, bool editable ) : IngredientListView( parent, db )
+{
+ addColumn( i18n( "Ingredient" ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+
+ KIconLoader *il = new KIconLoader;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il->loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_C );
+ kpop->insertItem( il->loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->polish();
+
+ delete il;
+
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem* ) ), this, SLOT( modIngredient( QListViewItem* ) ) );
+ connect( this, SIGNAL( itemRenamed( QListViewItem* ) ), this, SLOT( saveIngredient( QListViewItem* ) ) );
+ }
+}
+
+void StdIngredientListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdIngredientListView::createNew()
+{
+ CreateElementDialog * elementDialog = new CreateElementDialog( this, i18n( "New Ingredient" ) );
+
+ if ( elementDialog->exec() == QDialog::Accepted ) {
+ QString result = elementDialog->newElementName();
+
+ if ( checkBounds( result ) )
+ database->createNewIngredient( result ); // Create the new author in the database
+ }
+}
+
+void StdIngredientListView::remove
+ ()
+{
+ QListViewItem * it = currentItem();
+
+ if ( it ) {
+ int ingredientID = it->text( 1 ).toInt();
+
+ ElementList dependingRecipes;
+ database->findIngredientDependancies( ingredientID, &dependingRecipes );
+
+ if ( dependingRecipes.isEmpty() )
+ database->removeIngredient( ingredientID );
+ else { // Need Warning!
+ ListInfo list;
+ list.list = dependingRecipes;
+ list.name = i18n( "Recipes" );
+
+ DependanciesDialog warnDialog( this, list );
+ warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") );
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removeIngredient( ingredientID );
+ }
+ }
+}
+
+void StdIngredientListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item )
+ IngredientListView::rename( item, 0 );
+}
+
+void StdIngredientListView::createIngredient( const Element &ing )
+{
+ createElement(new QListViewItem( this, ing.name, QString::number( ing.id ) ));
+}
+
+void StdIngredientListView::removeIngredient( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 1 );
+ removeElement(item);
+}
+
+void StdIngredientListView::modIngredient( QListViewItem* i )
+{
+ if ( i )
+ IngredientListView::rename( i, 0);
+}
+
+void StdIngredientListView::saveIngredient( QListViewItem* i )
+{
+ if ( !checkBounds( i->text( 0 ) ) ) {
+ reload(ForceReload); //reset the changed text
+ return ;
+ }
+
+ int existing_id = database->findExistingIngredientByName( i->text( 0 ) );
+ int ing_id = i->text( 1 ).toInt();
+ if ( existing_id != -1 && existing_id != ing_id ) //category already exists with this label... merge the two
+ {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This ingredient already exists. Continuing will merge these two ingredients into one. Are you sure?" ) ) )
+ {
+ case KMessageBox::Continue: {
+ database->mergeIngredients( existing_id, ing_id );
+ break;
+ }
+ default:
+ reload(ForceReload);
+ break; //we have to reload because the ingredient was renamed, and we need to reset it
+ }
+ }
+ else {
+ database->modIngredient( ( i->text( 1 ) ).toInt(), i->text( 0 ) );
+ }
+}
+
+bool StdIngredientListView::checkBounds( const QString &name )
+{
+ if ( name.length() > uint(database->maxIngredientNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Ingredient name cannot be longer than %1 characters." ) ).arg( database->maxIngredientNameLength() ) );
+ return false;
+ }
+
+ return true;
+}
+
+
+
+IngredientCheckListView::IngredientCheckListView( QWidget *parent, RecipeDB *db ) : IngredientListView( parent, db )
+{
+ addColumn( i18n( "Ingredient" ) );
+
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+}
+
+void IngredientCheckListView::createIngredient( const Element &ing )
+{
+ createElement(new IngredientCheckListItem( this, ing ));
+}
+
+void IngredientCheckListView::removeIngredient( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 1 );
+ removeElement(item);
+}
+
+void IngredientCheckListView::load( int limit, int offset )
+{
+ IngredientListView::load(limit,offset);
+
+ for ( QValueList<Element>::const_iterator ing_it = m_selections.begin(); ing_it != m_selections.end(); ++ing_it ) {
+ QCheckListItem * item = ( QCheckListItem* ) findItem( QString::number( (*ing_it).id ), 1 );
+ if ( item ) {
+ item->setOn(true);
+ }
+ }
+}
+
+void IngredientCheckListView::stateChange(IngredientCheckListItem *it,bool on)
+{
+ if ( !reloading() ) {
+ if ( on )
+ m_selections.append(it->ingredient());
+ else
+ m_selections.remove(it->ingredient());
+ }
+}
+
+#include "ingredientlistview.moc"
diff --git a/krecipes/src/widgets/ingredientlistview.h b/krecipes/src/widgets/ingredientlistview.h
new file mode 100644
index 0000000..5487a11
--- /dev/null
+++ b/krecipes/src/widgets/ingredientlistview.h
@@ -0,0 +1,119 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef INGREDIENTLISTVIEW_H
+#define INGREDIENTLISTVIEW_H
+
+#include "dblistviewbase.h"
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+class KPopupMenu;
+class IngredientCheckListView;
+
+/**
+@author Unai Garro
+*/
+class IngredientCheckListItem: public QCheckListItem
+{
+public:
+ IngredientCheckListItem( IngredientCheckListView* qlv, const Element &ing );
+ IngredientCheckListItem( IngredientCheckListView* qlv, QListViewItem *after, const Element &ing );
+ ~IngredientCheckListItem( void );
+
+ int id( void ) const;
+ QString name( void ) const;
+ Element ingredient() const;
+
+ virtual QString text( int column ) const;
+
+protected:
+ virtual void stateChange( bool on );
+
+private:
+ Element *ingStored;
+ IngredientCheckListView *m_listview;
+};
+
+
+
+class IngredientListView : public DBListViewBase
+{
+ Q_OBJECT
+
+public:
+ IngredientListView( QWidget *parent, RecipeDB *db );
+
+protected slots:
+ virtual void createIngredient( const Element & ) = 0;
+ virtual void removeIngredient( int ) = 0;
+ virtual void load(int limit,int offset);
+
+protected:
+ virtual void init();
+
+private slots:
+ virtual void checkCreateIngredient( const Element & );
+};
+
+
+
+class StdIngredientListView : public IngredientListView
+{
+ Q_OBJECT
+
+public:
+ StdIngredientListView( QWidget *parent, RecipeDB *db, bool editable = false );
+
+protected:
+ virtual void createIngredient( const Element & );
+ virtual void removeIngredient( int );
+
+private slots:
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void createNew();
+ void remove
+ ();
+ void rename();
+
+ void modIngredient( QListViewItem* i );
+ void saveIngredient( QListViewItem* i );
+
+private:
+ bool checkBounds( const QString &name );
+
+ KPopupMenu *kpop;
+};
+
+
+
+class IngredientCheckListView : public IngredientListView
+{
+public:
+ IngredientCheckListView( QWidget *parent, RecipeDB *db );
+
+ virtual void stateChange(IngredientCheckListItem *,bool);
+
+ QValueList<Element> selections() const{ return m_selections; }
+
+protected:
+ virtual void createIngredient( const Element &ing );
+ virtual void removeIngredient( int );
+
+ virtual void load( int limit, int offset );
+
+private:
+ QValueList<Element> m_selections;
+};
+
+#endif //INGREDIENTLISTVIEW_H
diff --git a/krecipes/src/widgets/kdateedit.cpp b/krecipes/src/widgets/kdateedit.cpp
new file mode 100644
index 0000000..7bab504
--- /dev/null
+++ b/krecipes/src/widgets/kdateedit.cpp
@@ -0,0 +1,388 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2004 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301 USA
+*/
+
+#include <qapplication.h>
+#include <qlineedit.h>
+#include <qlistbox.h>
+#include <qvalidator.h>
+
+//#include <kcalendarsystem.h>
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <klocale.h>
+#include <kconfig.h>
+
+#include "kdateedit.h"
+
+QRect desktopGeometry(QWidget* w)
+{
+ QDesktopWidget *dw = QApplication::desktop();
+ if (dw->isVirtualDesktop()) {
+ KConfigGroup group(KGlobal::config(), "Windows");
+ if (group.readBoolEntry("XineramaEnabled", true) &&
+ group.readBoolEntry("XineramaPlacementEnabled", true)) {
+ if (w)
+ return dw->screenGeometry(dw->screenNumber(w));
+ else return dw->screenGeometry(-1);
+ } else {
+ return dw->geometry();
+ }
+ } else {
+ return dw->geometry();
+ }
+}
+
+class DateValidator : public QValidator
+{
+ public:
+ DateValidator( const QStringList &keywords, QWidget* parent, const char* name = 0 )
+ : QValidator( parent, name ), mKeywords( keywords )
+ {}
+
+ virtual State validate( QString &str, int& ) const
+ {
+ int length = str.length();
+
+ // empty string is intermediate so one can clear the edit line and start from scratch
+ if ( length <= 0 )
+ return Intermediate;
+
+ if ( mKeywords.contains( str.lower() ) )
+ return Acceptable;
+
+ bool ok = false;
+ KGlobal::locale()->readDate( str, &ok );
+ if ( ok )
+ return Acceptable;
+ else
+ return Intermediate;
+ }
+
+ private:
+ QStringList mKeywords;
+};
+
+KDateEdit::KDateEdit( QWidget *parent, const char *name )
+ : QComboBox( true, parent, name ),
+ mReadOnly( false ),
+ mDiscardNextMousePress( false )
+{
+ // need at least one entry for popup to work
+ setMaxCount( 1 );
+
+ mDate = QDate::currentDate();
+ QString today = KGlobal::locale()->formatDate( mDate, true );
+
+ insertItem( today );
+ setCurrentItem( 0 );
+ changeItem( today, 0 );
+ setMinimumSize( sizeHint() );
+
+ connect( lineEdit(), SIGNAL( returnPressed() ),
+ this, SLOT( lineEnterPressed() ) );
+ connect( this, SIGNAL( textChanged( const QString& ) ),
+ SLOT( slotTextChanged( const QString& ) ) );
+
+ mPopup = new KDatePickerPopup( KDatePickerPopup::DatePicker | KDatePickerPopup::Words );
+ mPopup->hide();
+ mPopup->installEventFilter( this );
+
+ connect( mPopup, SIGNAL( dateChanged( QDate ) ),
+ SLOT( dateSelected( QDate ) ) );
+
+ // handle keyword entry
+ setupKeywords();
+ lineEdit()->installEventFilter( this );
+
+ setValidator( new DateValidator( mKeywordMap.keys(), this ) );
+
+ mTextChanged = false;
+}
+
+KDateEdit::~KDateEdit()
+{
+ delete mPopup;
+ mPopup = 0;
+}
+
+void KDateEdit::setDate( const QDate& date )
+{
+ assignDate( date );
+ updateView();
+}
+
+QDate KDateEdit::date() const
+{
+ return mDate;
+}
+
+void KDateEdit::setReadOnly( bool readOnly )
+{
+ mReadOnly = readOnly;
+ lineEdit()->setReadOnly( readOnly );
+}
+
+bool KDateEdit::isReadOnly() const
+{
+ return mReadOnly;
+}
+
+void KDateEdit::popup()
+{
+ if ( mReadOnly )
+ return;
+
+ QRect desk = desktopGeometry( this );
+
+ QPoint popupPoint = mapToGlobal( QPoint( 0,0 ) );
+
+ int dateFrameHeight = mPopup->sizeHint().height();
+ if ( popupPoint.y() + height() + dateFrameHeight > desk.bottom() )
+ popupPoint.setY( popupPoint.y() - dateFrameHeight );
+ else
+ popupPoint.setY( popupPoint.y() + height() );
+
+ int dateFrameWidth = mPopup->sizeHint().width();
+ if ( popupPoint.x() + dateFrameWidth > desk.right() )
+ popupPoint.setX( desk.right() - dateFrameWidth );
+
+ if ( popupPoint.x() < desk.left() )
+ popupPoint.setX( desk.left() );
+
+ if ( popupPoint.y() < desk.top() )
+ popupPoint.setY( desk.top() );
+
+ if ( mDate.isValid() )
+ mPopup->setDate( mDate );
+ else
+ mPopup->setDate( QDate::currentDate() );
+
+ mPopup->popup( popupPoint );
+
+ // The combo box is now shown pressed. Make it show not pressed again
+ // by causing its (invisible) list box to emit a 'selected' signal.
+ // First, ensure that the list box contains the date currently displayed.
+ QDate date = parseDate();
+ assignDate( date );
+ updateView();
+ // Now, simulate an Enter to unpress it
+ QListBox *lb = listBox();
+ if (lb) {
+ lb->setCurrentItem(0);
+ QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, 0, 0);
+ QApplication::postEvent(lb, keyEvent);
+ }
+}
+
+void KDateEdit::dateSelected( QDate date )
+{
+ if (assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+
+ if ( date.isValid() ) {
+ mPopup->hide();
+ }
+ }
+}
+
+void KDateEdit::dateEntered( QDate date )
+{
+ if (assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+ }
+}
+
+void KDateEdit::lineEnterPressed()
+{
+ bool replaced = false;
+
+ QDate date = parseDate( &replaced );
+
+ if (assignDate( date ) ) {
+ if ( replaced )
+ updateView();
+
+ emit dateChanged( date );
+ }
+}
+
+QDate KDateEdit::parseDate( bool *replaced ) const
+{
+ QString text = currentText();
+ QDate result;
+
+ if ( replaced )
+ (*replaced) = false;
+
+ if ( text.isEmpty() )
+ result = QDate();
+ else if ( mKeywordMap.contains( text.lower() ) ) {
+ QDate today = QDate::currentDate();
+ int i = mKeywordMap[ text.lower() ];
+ if ( i >= 100 ) {
+ /* A day name has been entered. Convert to offset from today.
+ * This uses some math tricks to figure out the offset in days
+ * to the next date the given day of the week occurs. There
+ * are two cases, that the new day is >= the current day, which means
+ * the new day has not occurred yet or that the new day < the current day,
+ * which means the new day is already passed (so we need to find the
+ * day in the next week).
+ */
+ i -= 100;
+ int currentDay = today.dayOfWeek();
+ if ( i >= currentDay )
+ i -= currentDay;
+ else
+ i += 7 - currentDay;
+ }
+
+ result = today.addDays( i );
+ if ( replaced )
+ (*replaced) = true;
+ } else {
+ result = KGlobal::locale()->readDate( text );
+ }
+
+ return result;
+}
+
+bool KDateEdit::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object == lineEdit() ) {
+ // We only process the focus out event if the text has changed
+ // since we got focus
+ if ( (event->type() == QEvent::FocusOut) && mTextChanged ) {
+ lineEnterPressed();
+ mTextChanged = false;
+ } else if ( event->type() == QEvent::KeyPress ) {
+ // Up and down arrow keys step the date
+ QKeyEvent* keyEvent = (QKeyEvent*)event;
+
+ if ( keyEvent->key() == Qt::Key_Return ) {
+ lineEnterPressed();
+ return true;
+ }
+
+ int step = 0;
+ if ( keyEvent->key() == Qt::Key_Up )
+ step = 1;
+ else if ( keyEvent->key() == Qt::Key_Down )
+ step = -1;
+ if ( step && !mReadOnly ) {
+ QDate date = parseDate();
+ if ( date.isValid() ) {
+ date = date.addDays( step );
+ if ( assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+ return true;
+ }
+ }
+ }
+ }
+ } else {
+ // It's a date picker event
+ switch ( event->type() ) {
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseButtonPress: {
+ QMouseEvent *mouseEvent = (QMouseEvent*)event;
+ if ( !mPopup->rect().contains( mouseEvent->pos() ) ) {
+ QPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() );
+ if ( QApplication::widgetAt( globalPos, true ) == this ) {
+ // The date picker is being closed by a click on the
+ // KDateEdit widget. Avoid popping it up again immediately.
+ mDiscardNextMousePress = true;
+ }
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+void KDateEdit::mousePressEvent( QMouseEvent *event )
+{
+ if ( event->button() == Qt::LeftButton && mDiscardNextMousePress ) {
+ mDiscardNextMousePress = false;
+ return;
+ }
+
+ QComboBox::mousePressEvent( event );
+}
+
+void KDateEdit::slotTextChanged( const QString& )
+{
+ QDate date = parseDate();
+
+ if ( assignDate( date ) )
+ emit dateChanged( date );
+
+ mTextChanged = true;
+}
+
+void KDateEdit::setupKeywords()
+{
+ // Create the keyword list. This will be used to match against when the user
+ // enters information.
+ mKeywordMap.insert( i18n( "tomorrow" ), 1 );
+ mKeywordMap.insert( i18n( "today" ), 0 );
+ mKeywordMap.insert( i18n( "yesterday" ), -1 );
+
+ #if 0 //depends on KDE 3.2
+ QString dayName;
+ for ( int i = 1; i <= 7; ++i ) {
+ dayName = KGlobal::locale()->calendar()->weekDayName( i ).lower();
+ mKeywordMap.insert( dayName, i + 100 );
+ }
+ #endif
+}
+
+bool KDateEdit::assignDate( const QDate& date )
+{
+ mDate = date;
+ mTextChanged = false;
+ return true;
+}
+
+void KDateEdit::updateView()
+{
+ QString dateString;
+ if ( mDate.isValid() )
+ dateString = KGlobal::locale()->formatDate( mDate, true );
+
+ // We do not want to generate a signal here,
+ // since we explicitly setting the date
+ bool blocked = signalsBlocked();
+ blockSignals( true );
+ changeItem( dateString, 0 );
+ blockSignals( blocked );
+}
+
+#include "kdateedit.moc"
diff --git a/krecipes/src/widgets/kdateedit.h b/krecipes/src/widgets/kdateedit.h
new file mode 100644
index 0000000..bfe7d9e
--- /dev/null
+++ b/krecipes/src/widgets/kdateedit.h
@@ -0,0 +1,139 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2004 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301 USA
+*/
+
+#ifndef KDATEEDIT_H
+#define KDATEEDIT_H
+
+#include <qcombobox.h>
+#include <qdatetime.h>
+#include <qmap.h>
+
+#include "kdatepickerpopup.h"
+
+class QEvent;
+
+/**
+ A date editing widget that consists of an editable combo box.
+ The combo box contains the date in text form, and clicking the combo
+ box arrow will display a 'popup' style date picker.
+
+ This widget also supports advanced features like allowing the user
+ to type in the day name to get the date. The following keywords
+ are supported (in the native language): tomorrow, yesturday, today,
+ monday, tuesday, wednesday, thursday, friday, saturday, sunday.
+
+ @image html kdateedit.png "This is how it looks"
+
+ @author Cornelius Schumacher <schumacher@kde.org>
+ @author Mike Pilone <mpilone@slac.com>
+ @author David Jarvie <software@astrojar.org.uk>
+ @author Tobias Koenig <tokoe@kde.org>
+*/
+class KDateEdit : public QComboBox
+{
+ Q_OBJECT
+
+ public:
+ KDateEdit( QWidget *parent = 0, const char *name = 0 );
+ virtual ~KDateEdit();
+
+ /**
+ @return The date entered. This date could be invalid,
+ you have to check validity yourself.
+ */
+ QDate date() const;
+
+ /**
+ Sets whether the widget is read-only for the user. If read-only,
+ the date picker pop-up is inactive, and the displayed date cannot be edited.
+
+ @param readOnly True to set the widget read-only, false to set it read-write.
+ */
+ void setReadOnly( bool readOnly );
+
+ /**
+ @return True if the widget is read-only, false if read-write.
+ */
+ bool isReadOnly() const;
+
+ virtual void popup();
+
+ signals:
+ /**
+ This signal is emitted whenever the user modifies the date.
+ The passed date can be invalid.
+ */
+ void dateChanged( const QDate &date );
+
+ public slots:
+ /**
+ Sets the date.
+
+ @param date The new date to display. This date must be valid or
+ it will not be set
+ */
+ void setDate( const QDate &date );
+
+ protected slots:
+ void lineEnterPressed();
+ void slotTextChanged( const QString& );
+ void dateEntered( QDate );
+ void dateSelected( QDate );
+
+ protected:
+ virtual bool eventFilter( QObject*, QEvent* );
+ virtual void mousePressEvent( QMouseEvent* );
+
+ /**
+ Sets the date, without altering the display.
+ This method is used internally to set the widget's date value.
+ As a virtual method, it allows derived classes to perform additional validation
+ on the date value before it is set. Derived classes should return true if
+ QDate::isValid(@p date) returns false.
+
+ @param date The new date to set.
+ @return True if the date was set, false if it was considered invalid and
+ remains unchanged.
+ */
+ virtual bool assignDate( const QDate &date );
+
+ /**
+ Fills the keyword map. Reimplement it if you want additional
+ keywords.
+ */
+ void setupKeywords();
+
+ private:
+ QDate parseDate( bool* = 0 ) const;
+ void updateView();
+
+ KDatePickerPopup *mPopup;
+
+ QDate mDate;
+ bool mReadOnly;
+ bool mTextChanged;
+ bool mDiscardNextMousePress;
+
+ QMap<QString, int> mKeywordMap;
+};
+
+#endif
diff --git a/krecipes/src/widgets/kdatepickerpopup.cpp b/krecipes/src/widgets/kdatepickerpopup.cpp
new file mode 100644
index 0000000..5a58ccd
--- /dev/null
+++ b/krecipes/src/widgets/kdatepickerpopup.cpp
@@ -0,0 +1,123 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2004 Bram Schoenmakers <bramschoenmakers@kde.nl>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301 USA
+*/
+
+#include <qdatetime.h>
+#include <qpopupmenu.h>
+
+#include <klocale.h>
+
+#include "kdatepickerpopup.h"
+
+KDatePickerPopup::KDatePickerPopup( int items, const QDate &date, QWidget *parent,
+ const char *name )
+ : QPopupMenu( parent, name )
+{
+ mItems = items;
+
+ mDatePicker = new KDatePicker( this );
+ mDatePicker->setCloseButton( false );
+
+ connect( mDatePicker, SIGNAL( dateEntered( QDate ) ),
+ SLOT( slotDateChanged( QDate ) ) );
+ connect( mDatePicker, SIGNAL( dateSelected( QDate ) ),
+ SLOT( slotDateChanged( QDate ) ) );
+
+ mDatePicker->setDate( date );
+
+ buildMenu();
+}
+
+void KDatePickerPopup::buildMenu()
+{
+ if ( isVisible() ) return;
+ clear();
+
+ if ( mItems & DatePicker ) {
+ insertItem( mDatePicker );
+
+ if ( ( mItems & NoDate ) || ( mItems & Words ) )
+ insertSeparator();
+ }
+
+ if ( mItems & Words ) {
+ insertItem( i18n("&Today"), this, SLOT( slotToday() ) );
+ insertItem( i18n("&Yesterday"), this, SLOT( slotYesterday() ) );
+ insertItem( i18n("Last &Week"), this, SLOT( slotLastWeek() ) );
+ insertItem( i18n("Last M&onth"), this, SLOT( slotLastMonth() ) );
+
+ if ( mItems & NoDate )
+ insertSeparator();
+ }
+
+ if ( mItems & NoDate )
+ insertItem( i18n("No Date"), this, SLOT( slotNoDate() ) );
+}
+
+KDatePicker *KDatePickerPopup::datePicker() const
+{
+ return mDatePicker;
+}
+
+void KDatePickerPopup::setDate( const QDate &date )
+{
+ mDatePicker->setDate( date );
+}
+
+#if 0
+void KDatePickerPopup::setItems( int items )
+{
+ mItems = items;
+ buildMenu();
+}
+#endif
+
+void KDatePickerPopup::slotDateChanged( QDate date )
+{
+ emit dateChanged( date );
+ hide();
+}
+
+void KDatePickerPopup::slotToday()
+{
+ emit dateChanged( QDate::currentDate() );
+}
+
+void KDatePickerPopup::slotYesterday()
+{
+ emit dateChanged( QDate::currentDate().addDays( -1 ) );
+}
+
+void KDatePickerPopup::slotNoDate()
+{
+ emit dateChanged( QDate() );
+}
+
+void KDatePickerPopup::slotLastWeek()
+{
+ emit dateChanged( QDate::currentDate().addDays( -7 ) );
+}
+
+void KDatePickerPopup::slotLastMonth()
+{
+ emit dateChanged( QDate::currentDate().addMonths( -1 ) );
+}
+
+#include "kdatepickerpopup.moc"
diff --git a/krecipes/src/widgets/kdatepickerpopup.h b/krecipes/src/widgets/kdatepickerpopup.h
new file mode 100644
index 0000000..6a09a26
--- /dev/null
+++ b/krecipes/src/widgets/kdatepickerpopup.h
@@ -0,0 +1,102 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2004 Bram Schoenmakers <bramschoenmakers@kde.nl>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ Boston, MA 02110-1301 USA
+*/
+#ifndef KDATEPICKERPOPUP_H
+#define KDATEPICKERPOPUP_H
+
+#include <qdatetime.h>
+#include <qpopupmenu.h>
+
+#include <kdatepicker.h>
+
+/**
+ @short This menu helps the user to select a date quickly.
+
+ This menu helps the user to select a date quicly. It offers various ways of selecting, e.g. with a KDatePicker or with words like "Tomorrow".
+
+ The available items are:
+
+ @li NoDate: A menu-item with "No Date". If choosen, the datepicker will emit a null QDate.
+ @li DatePicker: Show a KDatePicker-widget.
+ @li Words: Show items like "Today", "Tomorrow" or "Next Week".
+
+ When supplying multiple items, separate each item with a bitwise OR.
+
+ @author Bram Schoenmakers <bram_s@softhome.net>
+*/
+class KDatePickerPopup: public QPopupMenu
+{
+ Q_OBJECT
+ public:
+ enum { NoDate = 1, DatePicker = 2, Words = 4 };
+
+ /**
+ A constructor for the KDatePickerPopup.
+
+ @param items List of all desirable items, separated with a bitwise OR.
+ @param date Initial date of datepicker-widget.
+ @param parent The object's parent.
+ @param name The object's name.
+ */
+ KDatePickerPopup( int items = 2, const QDate &date = QDate::currentDate(),
+ QWidget *parent = 0, const char *name = 0 );
+
+ /**
+ @return A pointer to the private variable mDatePicker, an instance of
+ KDatePicker.
+ */
+ KDatePicker *datePicker() const;
+
+ void setDate( const QDate &date );
+
+#if 0
+ /** Set items which should be shown and rebuilds the menu afterwards. Only if the menu is not visible.
+ @param items List of all desirable items, separated with a bitwise OR.
+ */
+ void setItems( int items = 1 );
+#endif
+ /** @return Returns the bitwise result of the active items in the popup. */
+ int items() const { return mItems; }
+
+ signals:
+
+ /**
+ This signal emits the new date (selected with datepicker or other
+ menu-items).
+ */
+ void dateChanged ( QDate );
+
+ protected slots:
+ void slotDateChanged ( QDate );
+
+ void slotToday();
+ void slotYesterday();
+ void slotLastWeek();
+ void slotLastMonth();
+ void slotNoDate();
+
+ private:
+ void buildMenu();
+
+ KDatePicker *mDatePicker;
+ int mItems;
+};
+
+#endif
diff --git a/krecipes/src/widgets/krelistview.cpp b/krecipes/src/widgets/krelistview.cpp
new file mode 100644
index 0000000..3df8fae
--- /dev/null
+++ b/krecipes/src/widgets/krelistview.cpp
@@ -0,0 +1,116 @@
+/***************************************************************************
+* Copyright (C) 2004-2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "krelistview.h"
+
+#include <kglobalsettings.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "widgets/dblistviewbase.h"
+
+KreListView::KreListView( QWidget *parent, const QString &title, bool filter, int filterCol, QWidget *embeddedWidget ) : QVBox( parent )
+{
+
+ filteredColumn = filterCol;
+ QWidget *header = this;
+ if ( filter || embeddedWidget ) {
+ header = new QHBox( this );
+ ( ( QHBox* ) header ) ->setSpacing( 15 );
+ }
+
+ if ( !title.isNull() ) {
+ listLabel = new QLabel( header );
+ listLabel->setFrameShape( QFrame::GroupBoxPanel );
+ listLabel->setFrameShadow( QFrame::Sunken );
+ listLabel->setPaletteForegroundColor( KGlobalSettings::highlightedTextColor() );
+ listLabel->setPaletteBackgroundColor( KGlobalSettings::highlightColor().light( 120 ) ); // 120, to match the kremenu settings
+ listLabel->setText( title );
+
+ }
+
+ if ( filter ) {
+ filterBox = new QHBox( header );
+ filterBox->setFrameShape( QFrame::Box );
+ filterBox->setMargin( 2 );
+ filterLabel = new QLabel( filterBox );
+ filterLabel->setText( i18n( "Search:" ) );
+ filterEdit = new KLineEdit( filterBox );
+ }
+
+
+ list = new KListView( this );
+ list->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
+ setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
+ setSpacing( 10 );
+
+
+ // If the user provides a widget, embed it into the header
+ if ( embeddedWidget )
+ embeddedWidget->reparent( header, QPoint( 0, 0 ) );
+ //Connect Signals & Slots
+ if ( filter ) {
+ connect( filterEdit, SIGNAL( textChanged( const QString& ) ), SIGNAL( textChanged(const QString&) ) );
+ connect( this, SIGNAL( textChanged( const QString& ) ), SLOT( filter( const QString& ) ) );
+ }
+}
+
+KreListView::~KreListView()
+{}
+
+void KreListView::filter( const QString& s )
+{
+ for ( QListViewItem * it = list->firstChild();it;it = it->nextSibling() ) {
+ if ( it->rtti() == NEXTLISTITEM_RTTI || it->rtti() == PREVLISTITEM_RTTI )
+ continue;
+
+ if ( s.isEmpty() ) // Don't filter if the filter text is empty
+ {
+ it->setVisible( true );
+ }
+ else
+ {
+
+ if ( it->text( filteredColumn ).contains( s, false ) )
+ it->setVisible( true );
+ else
+ it->setVisible( false );
+
+ }
+
+
+ }
+}
+
+void KreListView::refilter()
+{
+ if ( !filterEdit->text().isEmpty() ) {
+ emit textChanged( filterEdit->text() );
+ }
+}
+
+void KreListView::setCustomFilter( QObject *receiver, const char *slot )
+{
+ connect( this, SIGNAL( textChanged( const QString& ) ), receiver, slot );
+}
+
+void KreListView::setListView( DBListViewBase *list_view )
+{
+ delete list;
+
+ connect( list_view, SIGNAL( nextGroupLoaded() ), SLOT( refilter() ) );
+ connect( list_view, SIGNAL( prevGroupLoaded() ), SLOT( refilter() ) );
+ list = list_view;
+}
+
+#include "krelistview.moc"
diff --git a/krecipes/src/widgets/krelistview.h b/krecipes/src/widgets/krelistview.h
new file mode 100644
index 0000000..d6ad342
--- /dev/null
+++ b/krecipes/src/widgets/krelistview.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KRELISTVIEW_H
+#define KRELISTVIEW_H
+
+#include <qlabel.h>
+#include <qvbox.h>
+#include <klistview.h>
+#include <klineedit.h>
+
+class DBListViewBase;
+
+/**
+@author Unai Garro
+*/
+
+class KreListView: public QVBox
+{
+ Q_OBJECT
+public:
+
+ KreListView( QWidget *parent, const QString &title = QString::null, bool filter = false, int filterCol = 0, QWidget *embeddedWidget = 0 );
+ ~KreListView();
+ KListView *listView()
+ {
+ return list;
+ }
+
+ void setListView( KListView *list_view )
+ {
+ delete list;
+ list = list_view;
+ }
+ void setListView( DBListViewBase *list_view );
+
+ void setCustomFilter( QObject *receiver, const char *slot );
+ QString filterText() const { return filterEdit->text(); }
+
+public slots:
+ void refilter();
+
+signals:
+ void textChanged( const QString & );
+
+private:
+ QHBox *filterBox;
+ QLabel *listLabel;
+ int filteredColumn;
+ QLabel *filterLabel;
+ KLineEdit *filterEdit;
+ KListView *list;
+
+private slots:
+ void filter( const QString& s );
+};
+
+#endif
diff --git a/krecipes/src/widgets/kremenu.cpp b/krecipes/src/widgets/kremenu.cpp
new file mode 100644
index 0000000..892ff75
--- /dev/null
+++ b/krecipes/src/widgets/kremenu.cpp
@@ -0,0 +1,639 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as publishfed by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "kremenu.h"
+
+#include <qbitmap.h>
+#include <qcursor.h>
+#include <qfont.h>
+#include <qimage.h>
+#include <qobjectlist.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qsignalmapper.h>
+
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kimageeffect.h>
+#include <klocale.h>
+#include <kpixmap.h>
+#include <kpixmapeffect.h>
+
+KreMenu::KreMenu( QWidget *parent, const char *name ) :
+#if QT_VERSION >= 0x030200
+ QWidget( parent, name, Qt::WNoAutoErase )
+#else
+ QWidget( parent, name )
+#endif
+{
+ Menu newMenu;
+
+ mainMenuId = menus.append( newMenu );
+
+ currentMenuId = mainMenuId;
+ m_currentMenu = &( *currentMenuId );
+
+ setMouseTracking( true );
+ setFocusPolicy( QWidget::StrongFocus );
+ setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Preferred );
+}
+
+
+KreMenu::~KreMenu()
+{}
+
+void KreMenu::childEvent ( QChildEvent *e )
+{
+ if ( e->type() == QChildEvent::ChildInserted ) {
+
+ QObject * child = e->child();
+ if ( child->inherits( "KreMenuButton" ) ) {
+ KreMenuButton * button = ( KreMenuButton* ) child;
+
+ Menu *buttonMenu = &( *( button->menuId ) );
+
+
+
+ if ( !( buttonMenu->activeButton ) ) // Highlight the button if it's the first in the menu
+ {
+ button->setActive( true );
+ buttonMenu->activeButton = button;
+ }
+
+ buttonMenu->addButton( button );
+
+ if ( buttonMenu != m_currentMenu )
+ button->hide();
+ else
+ button->show();
+
+ connect ( button, SIGNAL( clicked( KreMenuButton* ) ), this, SLOT( collectClicks( KreMenuButton* ) ) );
+ }
+ }
+ else if ( e->type() == QChildEvent::ChildRemoved ) {
+ QObject * child = e->child();
+
+ KreMenuButton *button = ( KreMenuButton* ) child;
+ if ( m_currentMenu->positionList.find( button ) != m_currentMenu->positionList.end() ) // Ensure that what was removed was a button
+ {
+ // Remove the button from the list first
+ int pos = m_currentMenu->positionList[ button ]; // FIXME: this works only if the button is removed from the main menu
+ m_currentMenu->widgetList.remove( pos ); // FIXME: this works only if the button is removed from the main menu
+ m_currentMenu->positionList.remove( button ); // FIXME: this works only if the button is removed from the main menu
+
+ // Now recalculate the position of the next button
+ ( m_currentMenu->widgetNumber ) --; // FIXME: this works only if the button is removed from the main menu
+
+ KreMenuButton *lastButton = m_currentMenu->widgetList[ ( m_currentMenu->widgetNumber ) - 1 ];
+ if ( lastButton )
+ m_currentMenu->childPos = lastButton->y() + lastButton->height();
+ m_currentMenu->activeButton = 0;
+
+ setMinimumWidth( minimumSizeHint().width() + 10 ); //update the minimum width
+ }
+
+ }
+ QWidget::childEvent( e );
+}
+
+void KreMenu::collectClicks( KreMenuButton *w )
+{
+ setFocus();
+
+ highlightButton( w );
+
+ // Emit signal indicating button activation with button ID
+ KrePanel panel = w->getPanel();
+ emit clicked( panel );
+}
+
+MenuId KreMenu::createSubMenu( const QString &title, const QString &icon )
+{
+
+ // Create the new menu
+ Menu newMenu;
+ MenuId id = menus.append( newMenu );
+
+ // Add a button to the main menu for this submenu
+ KIconLoader il;
+ KreMenuButton *newMenuButton = new KreMenuButton( this );
+ newMenuButton->subMenuId = id;
+ newMenuButton->setTitle( title );
+ newMenuButton->setIconSet( il.loadIconSet( icon, KIcon::Panel ) );
+
+ // Add a button to the submenu to go back to the top menu
+ KreMenuButton *newSubMenuButton = new KreMenuButton( this );
+ newSubMenuButton->menuId = id;
+ newSubMenuButton->subMenuId = mainMenuId;
+ newSubMenuButton->setTitle( i18n( "Up" ) );
+ newSubMenuButton->setIconSet( il.loadIconSet( "1uparrow", KIcon::Panel ) );
+
+ connect( newMenuButton, SIGNAL( clicked( MenuId ) ), this, SLOT( showMenu( MenuId ) ) );
+ connect( newSubMenuButton, SIGNAL( clicked( MenuId ) ), this, SLOT( showMenu( MenuId ) ) );
+
+
+ return id;
+}
+
+void KreMenu::highlightButton( KreMenuButton *button )
+{
+ MenuId buttonMenuId = button->menuId;
+ Menu *buttonMenu = &( *buttonMenuId );
+
+ //Deactivate the old button
+ if ( buttonMenu->activeButton ) {
+ buttonMenu->activeButton->setActive( false );
+ buttonMenu->activeButton->update();
+ }
+
+ //Activate the new button
+
+ button->setActive( true );
+ button->update();
+ buttonMenu->activeButton = button;
+}
+
+void KreMenu::keyPressEvent( QKeyEvent *e )
+{
+ switch ( e->key() ) {
+ case Qt::Key_Up: {
+ int current_index = m_currentMenu->positionList[ m_currentMenu->activeButton ];
+ if ( current_index > 0 ) {
+ highlightButton( m_currentMenu->widgetList[ current_index - 1 ] );
+
+ //simulate a mouse click
+ QMouseEvent me( QEvent::MouseButtonPress, QPoint(), 0, 0 );
+ KApplication::sendEvent( m_currentMenu->activeButton, &me );
+
+ e->accept();
+ }
+ break;
+ }
+ case Qt::Key_Down: {
+ int current_index = m_currentMenu->positionList[ m_currentMenu->activeButton ];
+ if ( current_index < int( m_currentMenu->positionList.count() ) - 1 ) {
+ highlightButton( m_currentMenu->widgetList[ current_index + 1 ] );
+
+ //simulate a mouse click
+ QMouseEvent me( QEvent::MouseButtonPress, QPoint(), 0, 0 );
+ KApplication::sendEvent( m_currentMenu->activeButton, &me );
+
+ e->accept();
+ }
+ break;
+ }
+ case Qt::Key_Enter:
+ case Qt::Key_Return:
+ case Qt::Key_Space: {
+ //simulate a mouse click
+ QMouseEvent me( QEvent::MouseButtonPress, QPoint(), 0, 0 );
+ KApplication::sendEvent( m_currentMenu->activeButton, &me );
+
+ e->accept();
+ break;
+ }
+ default:
+ e->ignore();
+ }
+}
+
+QSize KreMenu::sizeHint() const
+{
+ return minimumSizeHint();
+}
+
+//the minimum size hint will be the minimum size hint of the largest child
+QSize KreMenu::minimumSizeHint() const
+{
+ int width = 30;
+
+ QObjectList *childElements = queryList( 0, 0, false, false ); //only first-generation children (not recursive)
+ QObjectListIterator it( *childElements );
+
+ QObject *obj;
+ while ( ( obj = it.current() ) != 0 ) {
+ ++it;
+
+ if ( obj->isWidgetType() ) {
+ int obj_width_hint = ( ( QWidget* ) obj ) ->minimumSizeHint().width();
+
+ if ( obj_width_hint > width )
+ width = obj_width_hint;
+ }
+ }
+
+ return QSize( width, 150 );
+}
+
+void KreMenu::paintEvent( QPaintEvent * )
+{
+ // Make sure the size is bigger than the minimum necessary
+ //if (minimumWidth() <45) setMinimumWidth(45); // FIXME: can somehow setMinimumWidth be restricted? This may not be the best place to do this
+
+ // Get gradient colors
+ QColor c = colorGroup().button();
+ QColor c1 = c.dark( 130 );
+ QColor c2 = c.light( 120 );
+
+ // Draw the gradient
+ KPixmap kpm;
+ kpm.resize( size() );
+ KPixmapEffect::unbalancedGradient ( kpm, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 );
+
+ // Draw the handle
+ QPainter painter( &kpm );
+ painter.setPen( c1 );
+ painter.drawLine( width() - 5, 20, width() - 5, height() - 20 );
+ painter.end();
+
+ //Set the border transparent using a mask
+ QBitmap mask( kpm.size() );
+ mask.fill( Qt::color0 );
+ painter.begin( &mask );
+ painter.setPen( Qt::color1 );
+ painter.setBrush( Qt::color1 );
+ painter.drawRoundRect( 0, 0, width(), height(), ( int ) ( 2.0 / width() * height() ), 2 );
+ painter.end();
+ kpm.setMask( mask );
+
+ //Draw the border line
+ painter.begin( &kpm );
+ painter.setPen( c1 );
+ painter.drawRoundRect( 0, 0, width(), height(), ( int ) ( 2.0 / width() * height() ), 2 );
+
+ //Draw the top line bordering with the first button
+ if ( m_currentMenu->activeButton ) // draw only if there's a button
+ {
+ int w = m_currentMenu->activeButton->width();
+ painter.setPen( c1 );
+ painter.drawLine( w / 5, 8, w - 1, 8 );
+ painter.setPen( c2 );
+ painter.drawLine( w / 5, 9, w - 1, 9 );
+ }
+
+ painter.end();
+
+ // Copy the pixmap to the widget
+ bitBlt( this, 0, 0, &kpm );
+}
+
+void KreMenu::resizeEvent( QResizeEvent* e )
+{
+ emit resized( ( e->size() ).width(), ( e->size() ).height() );
+}
+
+void KreMenu::showMenu( MenuId id )
+{
+
+ // Hide the buttons in the current menu
+ // and show the ones in the new menu
+
+ QObjectList * childElements = queryList();
+ QObjectListIterator it( *childElements );
+
+ QObject *obj;
+ while ( ( obj = it.current() ) != 0 ) {
+ ++it;
+ if ( obj->inherits( "KreMenuButton" ) ) {
+ KreMenuButton * button = ( KreMenuButton* ) obj;
+ if ( button->menuId == currentMenuId )
+ button->hide();
+ else if ( button->menuId == id )
+ button->show();
+ }
+ }
+
+
+ // Set the new menu as current
+ currentMenuId = id;
+ m_currentMenu = &( *( currentMenuId ) );
+}
+
+
+
+
+
+KreMenuButton::KreMenuButton( KreMenu *parent, KrePanel _panel, MenuId id, const char *name ) :
+#if QT_VERSION >= 0x030200
+ QWidget( parent, name, Qt::WNoAutoErase ),
+#else
+ QWidget( parent, name ),
+#endif
+ panel( _panel )
+{
+ icon = 0;
+ highlighted = false;
+ text = QString::null;
+
+ if ( id == 0 )
+ menuId = parent->mainMenu();
+ else
+ menuId = id;
+
+ subMenuId = 0; // By default it's not a submenu button
+
+ resize( parent->size().width(), 55 );
+ connect ( parent, SIGNAL( resized( int, int ) ), this, SLOT( rescale() ) );
+ connect( this, SIGNAL( clicked() ), this, SLOT( forwardClicks() ) );
+ setCursor( QCursor( KCursor::handCursor() ) );
+}
+
+
+KreMenuButton::~KreMenuButton()
+{
+ delete icon;
+}
+
+void KreMenuButton::setTitle( const QString &s )
+{
+ text = s;
+
+#if 0 //this causes problems for the button to go back to editing a recipe
+ //adjust text to two lines if needed
+ if ( fontMetrics().width( text ) > 110 ) {
+ text.replace( ' ', "\n" );
+ }
+#endif
+
+ setMinimumWidth( minimumSizeHint().width() );
+ if ( parentWidget() ->minimumWidth() < minimumSizeHint().width() )
+ parentWidget() ->setMinimumWidth( minimumSizeHint().width() + 10 );
+
+ update();
+}
+
+void KreMenuButton::mousePressEvent ( QMouseEvent * )
+{
+ emit clicked();
+}
+
+void KreMenuButton::rescale()
+{
+ resize( parentWidget() ->width() - 10, height() );
+}
+QSize KreMenuButton::sizeHint() const
+{
+ if ( parentWidget() )
+ return ( QSize( parentWidget() ->size().width() - 10, 40 ) );
+ else
+ return QSize( 100, 30 );
+}
+
+QSize KreMenuButton::minimumSizeHint() const
+{
+ int text_width = QMAX( fontMetrics().width( text.section( '\n', 0, 0 ) ), fontMetrics().width( text.section( '\n', 1, 1 ) ) );
+
+ if ( icon )
+ return QSize( 40 + icon->width() + text_width, 30 );
+ else
+ return QSize( 40 + text_width, 30 );
+}
+
+void KreMenuButton::paintEvent( QPaintEvent * )
+{
+ if ( !isShown() )
+ return ;
+ // First draw the gradient
+ int darken = 130, lighten = 120;
+ QColor c1, c2, c1h, c2h; //non-highlighted and highlighted versions
+
+ // Set the gradient colors
+
+ c1 = colorGroup().button().dark( darken );
+ c2 = colorGroup().button().light( lighten );
+
+ if ( highlighted ) {
+ darken -= 10;
+ lighten += 10;
+ c1h = KGlobalSettings::highlightColor().dark( darken );
+ c2h = KGlobalSettings::highlightColor().light( lighten );
+ }
+
+ // draw the gradient now
+
+ QPainter painter;
+ KPixmap kpm;
+ kpm.resize( ( ( QWidget * ) parent() ) ->size() ); // use parent's same size to obtain the same gradient
+
+ if ( !highlighted ) {
+
+ // first the gradient
+ KPixmapEffect::unbalancedGradient ( kpm, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 );
+
+ }
+ else {
+
+ // top gradient (highlighted)
+ kpm.resize( width(), height() );
+ KPixmapEffect::unbalancedGradient ( kpm, c2h, c1h, KPixmapEffect::HorizontalGradient, -150, -150 );
+ // low gradient besides the line (not hightlighted)
+ KPixmap kpmb;
+ kpmb.resize( width(), 2 );
+ KPixmapEffect::unbalancedGradient ( kpmb, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 );
+ // mix the two
+ bitBlt( &kpm, 0, height() - 2, &kpmb );
+
+ }
+
+ // Draw the line
+ painter.begin( &kpm );
+ painter.setPen( colorGroup().button().dark( darken ) );
+ painter.drawLine( width() / 5, height() - 2, width() - 1, height() - 2 );
+ painter.setPen( colorGroup().button().light( lighten ) );
+ painter.drawLine( width() / 5, height() - 1, width() - 1, height() - 1 );
+ painter.end();
+
+
+ // Now Add the icon
+
+ painter.begin( &kpm );
+ int xPos, yPos;
+ if ( icon ) {
+ // Set the icon's desired horizontal position
+
+ xPos = 10;
+ yPos = 0;
+
+
+ // Make sure it fits in the area
+ // If not, resize and reposition horizontally to be centered
+
+ QPixmap scaledIcon = *icon;
+
+ if ( ( icon->height() > height() ) || ( icon->width() > width() / 3 ) ) // Nice effect, make sure you take less than half in width and fit in height (try making the menu very short in width)
+ {
+ QImage image;
+ image = ( *icon );
+ scaledIcon.convertFromImage( image.smoothScale( width() / 3, height(), QImage::ScaleMin ) );
+ }
+
+ // Calculate the icon's vertical position
+
+ yPos = ( height() - scaledIcon.height() ) / 2 - 1;
+
+
+ // Now draw it
+
+ painter.drawPixmap( xPos, yPos, scaledIcon );
+
+ xPos += scaledIcon.width(); // increase it to place the text area correctly
+ }
+
+ painter.end();
+
+ // If it's highlighted, draw a rounded area around the text
+
+ // Calculate the rounded area
+
+ int areax = xPos + 10;
+ int areah = fontMetrics().height() * ( text.contains( '\n' ) + 1 ) + fontMetrics().lineSpacing() * text.contains( '\n' ) + 6; // Make sure the area is sensible for text and adjust for multiple lines
+
+ int areaw = width() - areax - 10;
+
+ if ( areah > ( height() - 4 ) ) {
+ areah = height() - 4; // Limit to button height
+ }
+
+ int areay = ( height() - areah - 2 ) / 2 + 1; // Center the area vertically
+
+
+ // Calculate roundness
+
+ int roundy = 99, roundx = ( int ) ( ( float ) roundy * areah / areaw ); //Make corners round
+
+
+ if ( highlighted && areaw > 0 ) // If there is no space for the text area do not draw it
+ {
+
+ // Draw the gradient
+ KPixmap area;
+ area.resize( areaw, areah );
+
+
+ KPixmapEffect::gradient( area, c2h.light( 150 ), c1h.light( 150 ), KPixmapEffect::VerticalGradient );
+
+ painter.begin( &area );
+ painter.setPen( c1h );
+ painter.setBrush( Qt::NoBrush );
+ painter.drawRoundRect( 0, 0, areaw, areah, roundx, roundy );
+ painter.end();
+
+ // Make it round
+ QBitmap mask( QSize( areaw, areah ) );
+ mask.fill( Qt::color0 );
+ painter.begin( &mask );
+ painter.setPen( Qt::color1 );
+ painter.setBrush( Qt::color1 );
+ painter.drawRoundRect( 0, 0, areaw, areah, roundx, roundy );
+ painter.end();
+ area.setMask( mask );
+
+ // Copy it to the button
+ bitBlt( &kpm, areax, areay, &area );
+ }
+
+ // Finally, draw the text besides the icon
+ QRect r = rect();
+ r.setLeft( areax + 5 );
+ r.setWidth( areaw - 10 );
+
+ painter.begin( &kpm );
+ if ( highlighted )
+ painter.setPen( KGlobalSettings::highlightedTextColor() );
+ else
+ painter.setPen( KGlobalSettings::textColor() );
+ painter.setClipRect( r );
+ painter.drawText( r, Qt::AlignVCenter, text );
+ painter.end();
+
+ // Copy the offscreen button to the widget
+ bitBlt( this, 0, 0, &kpm, 0, 0, width(), height() ); // Copy the image with correct button size (button is already smaller than parent in width to leave space for the handle, so no need to use -10)
+
+}
+
+void KreMenuButton::setIconSet( const QIconSet &is )
+{
+ delete icon;
+
+ icon = new QPixmap( is.pixmap( QIconSet::Small, QIconSet::Normal, QIconSet::On ) );
+
+ setMinimumWidth( minimumSizeHint().width() );
+ if ( parentWidget() ->minimumWidth() < minimumSizeHint().width() )
+ parentWidget() ->setMinimumWidth( minimumSizeHint().width() + 10 );
+}
+
+Menu::Menu( void )
+{
+ childPos = 10; // Initial button is on top (10px), then keep scrolling down
+ widgetNumber = 0; // Initially we have no buttons
+ activeButton = 0; // Button that is highlighted
+}
+
+
+Menu::Menu( const Menu &m )
+{
+ activeButton = m.activeButton;
+ childPos = m.childPos;
+ widgetNumber = m.widgetNumber;
+
+ copyMap( positionList, m.positionList );
+ copyMap( widgetList, m.widgetList );
+}
+
+Menu::~Menu( void )
+{}
+
+Menu& Menu::operator=( const Menu &m )
+{
+
+ activeButton = m.activeButton;
+ childPos = m.childPos;
+ widgetNumber = m.widgetNumber;
+
+ copyMap( positionList, m.positionList );
+ copyMap( widgetList, m.widgetList );
+
+ return *this;
+}
+
+
+void Menu::addButton( KreMenuButton* button )
+{
+ button->move( 0, childPos );
+ button->rescale();
+ childPos += button->height();
+ positionList[ button ] = widgetNumber; // Store index for this widget, and increment number
+ widgetList[ widgetNumber ] = button; // Store the button in the list (the inverse mapping of the previous one)
+ widgetNumber++;
+}
+
+void Menu::copyMap( QMap <int, KreMenuButton*> &destMap, const QMap <int, KreMenuButton*> &origMap )
+{
+ QMap<int, KreMenuButton*>::ConstIterator it;
+ destMap.clear();
+ for ( it = origMap.begin(); it != origMap.end(); ++it ) {
+ destMap[ it.key() ] = it.data();
+ }
+}
+
+void Menu::copyMap( QMap <KreMenuButton*, int> &destMap, const QMap <KreMenuButton*, int> &origMap )
+{
+ QMap<KreMenuButton*, int>::ConstIterator it;
+ destMap.clear();
+ for ( it = origMap.begin(); it != origMap.end(); ++it ) {
+ destMap[ it.key() ] = it.data();
+ }
+}
+
+#include "kremenu.moc"
diff --git a/krecipes/src/widgets/kremenu.h b/krecipes/src/widgets/kremenu.h
new file mode 100644
index 0000000..24cdc74
--- /dev/null
+++ b/krecipes/src/widgets/kremenu.h
@@ -0,0 +1,170 @@
+/***************************************************************************
+* Copyright (C) 2003 by *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KREMENU_H
+#define KREMENU_H
+
+#include <qbuttongroup.h>
+#include <qevent.h>
+#include <qiconset.h>
+#include <qmap.h>
+#include <qpushbutton.h>
+#include <qstring.h>
+
+#include "krecipesview.h" //for KrePanel enum
+
+
+/**
+* @author Unai Garro
+* @author Bosselut Cyril
+*/
+
+class Menu;
+class KreMenu;
+class KreMenuButton;
+typedef QValueList <Menu>::Iterator MenuId;
+
+
+class Menu
+{
+public:
+ // Methods
+
+ Menu( void );
+ Menu( const Menu &m );
+ ~Menu( void );
+ void addButton( KreMenuButton *button );
+ Menu& operator=( const Menu &m );
+
+ // Variables
+
+ QMap <KreMenuButton*, int> positionList; // Stores the indexes for the widgets
+ QMap <int, KreMenuButton*> widgetList; // Stores the widgets for each position (just the inverse mapping)
+ KreMenuButton* activeButton; // Indicates which button is highlighted
+ int childPos;
+ int widgetNumber;
+private:
+ // Methods
+ void copyMap( QMap <int, KreMenuButton*> &destMap, const QMap <int, KreMenuButton*> &origMap );
+ void copyMap( QMap <KreMenuButton*, int> &destMap, const QMap <KreMenuButton*, int> &origMap );
+};
+
+
+class KreMenu : public QWidget
+{
+ Q_OBJECT
+public:
+ KreMenu( QWidget *parent = 0, const char *name = 0 );
+ ~KreMenu();
+
+ MenuId createSubMenu( const QString &title, const QString &icon );
+ MenuId mainMenu( void )
+ {
+ return mainMenuId;
+ }
+ MenuId currentMenu( void )
+ {
+ return currentMenuId;
+ }
+ QSize sizeHint() const;
+ QSize minimumSizeHint() const;
+ void resizeEvent( QResizeEvent* e );
+ void highlightButton( KreMenuButton *button );
+
+
+protected:
+
+ virtual void paintEvent ( QPaintEvent *e );
+ virtual void childEvent ( QChildEvent *e );
+ virtual void keyPressEvent( QKeyEvent *e );
+
+private:
+ //Variables
+ QValueList <Menu> menus;
+ MenuId mainMenuId;
+ MenuId currentMenuId;
+ Menu *m_currentMenu;
+
+signals:
+ void resized( int, int );
+ void clicked( KrePanel );
+
+private slots:
+ void collectClicks( KreMenuButton *w );
+ void showMenu( MenuId id );
+
+};
+
+class KreMenuButton: public QWidget
+{
+ Q_OBJECT
+public:
+ KreMenuButton( KreMenu *parent, KrePanel panel = KrePanel( -1 ), MenuId id = 0, const char *name = 0 );
+
+ ~KreMenuButton();
+
+ QSize sizeHint() const;
+ QSize minimumSizeHint() const;
+
+ QString title( void )
+ {
+ return text;
+ }
+ void setActive( bool active = true )
+ {
+ highlighted = active;
+ }
+ void setIconSet( const QIconSet &is );
+ MenuId menuId;
+ MenuId subMenuId;
+
+ KrePanel getPanel() const
+ {
+ return panel;
+ }
+
+signals:
+ void resized( int, int );
+ void clicked( void );
+ void clicked( KreMenuButton* ); // sent together with clicked()
+ void clicked( MenuId ); // sent together with clicked()
+
+public slots:
+ void setTitle( const QString &s );
+ void rescale( void );
+
+private:
+ // Button parts
+ QPixmap* icon;
+ QString text;
+ bool highlighted;
+
+ KrePanel panel;
+
+private slots:
+
+ void forwardClicks( void )
+ {
+ emit clicked( this );
+ if ( subMenuId != 0 )
+ emit clicked( subMenuId );
+ }
+
+protected:
+
+ virtual void paintEvent( QPaintEvent *e );
+ virtual void mousePressEvent ( QMouseEvent * e );
+
+};
+
+
+
+#endif
diff --git a/krecipes/src/widgets/kreruler.cpp b/krecipes/src/widgets/kreruler.cpp
new file mode 100644
index 0000000..69c63a6
--- /dev/null
+++ b/krecipes/src/widgets/kreruler.cpp
@@ -0,0 +1,1049 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ Copyright (C) 2005 Jason Kivlighn <jkivlighn@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+// Description: Ruler (header)
+
+/******************************************************************/
+
+#include "kreruler.h"
+
+#include <klocale.h>
+#include <kglobalsettings.h>
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <qcursor.h>
+#include <qpainter.h>
+#include <qpopupmenu.h>
+#include <qtooltip.h>
+
+#include "krepagelayout.h"
+
+class KoRulerPrivate {
+public:
+ KoRulerPrivate() {
+ }
+ ~KoRulerPrivate() {}
+
+ QWidget *canvas;
+ int flags;
+ int oldMx, oldMy;
+ bool whileMovingBorderLeft, whileMovingBorderRight;
+ bool whileMovingBorderTop, whileMovingBorderBottom;
+ QPixmap pmFirst, pmLeft;
+ KoPageLayout layout;
+ KoTabulatorList tabList;
+ // Do we have to remove a certain tab in the DC Event?
+ KoTabulator removeTab;
+ // The tab we're moving / clicking on - basically only valid between press and release time
+ KoTabulator currTab;
+ // The action we're currently doing - basically only valid between press and release time
+ KoRuler::Action action;
+ QPopupMenu *rb_menu;
+ int mRemoveTab, mPageLayout; // menu item ids
+ int frameEnd;
+ double i_right;
+ bool m_bReadWrite;
+ bool doubleClickedIndent;
+ bool rtl;
+ bool mousePressed;
+};
+
+// Equality test for tab positions in particular
+static inline bool equals( double a, double b ) {
+ return kAbs( a - b ) < 1E-4;
+}
+
+
+/******************************************************************/
+/* Class: KoRuler */
+/******************************************************************/
+
+const int KoRuler::F_TABS = 1;
+const int KoRuler::F_INDENTS = 2;
+const int KoRuler::F_HELPLINES = 4;
+const int KoRuler::F_NORESIZE = 8;
+
+/*================================================================*/
+KoRuler::KoRuler( QWidget *_parent, QWidget *_canvas, Orientation _orientation,
+ const KoPageLayout& _layout, int _flags, KoUnit::Unit _unit )
+ : QFrame( _parent ), buffer( width(), height() ), m_zoom(1.0), m_1_zoom(1.0),
+ m_unit( _unit )
+{
+ setWFlags( WResizeNoErase | WRepaintNoErase );
+ setFrameStyle( MenuBarPanel );
+
+ d=new KoRulerPrivate();
+
+ d->canvas = _canvas;
+ orientation = _orientation;
+ d->layout = _layout;
+ d->flags = _flags;
+
+ d->m_bReadWrite=true;
+ d->doubleClickedIndent=false;
+ diffx = 0;
+ diffy = 0;
+ i_left=0.0;
+ i_first=0.0;
+ d->i_right=0.0;
+
+ setMouseTracking( true );
+ d->mousePressed = false;
+ d->action = A_NONE;
+
+ d->oldMx = 0;
+ d->oldMy = 0;
+ d->rtl = false;
+
+ showMPos = false;
+ mposX = 0;
+ mposY = 0;
+ gridSize=0.0;
+ hasToDelete = false;
+ d->whileMovingBorderLeft = d->whileMovingBorderRight = d->whileMovingBorderTop = d->whileMovingBorderBottom = false;
+
+ d->pmFirst = UserIcon( "koRulerFirst" );
+ d->pmLeft = UserIcon( "koRulerLeft" );
+ d->currTab.type = T_INVALID;
+
+ d->removeTab.type = T_INVALID;
+ if ( orientation == Qt::Horizontal ) {
+ frameStart = qRound( zoomIt(d->layout.ptLeft) );
+ d->frameEnd = qRound( zoomIt(d->layout.ptWidth - d->layout.ptRight) );
+ } else {
+ frameStart = qRound( zoomIt(d->layout.ptTop) );
+ d->frameEnd = qRound( zoomIt(d->layout.ptHeight - d->layout.ptBottom) );
+ }
+ m_bFrameStartSet = false;
+
+ setupMenu();
+
+ // For compatibility, emitting doubleClicked shall emit openPageLayoutDia
+ connect( this, SIGNAL( doubleClicked() ), this, SIGNAL( openPageLayoutDia() ) );
+}
+
+/*================================================================*/
+KoRuler::~KoRuler()
+{
+ delete d->rb_menu;
+ delete d;
+}
+
+void KoRuler::setPageLayoutMenuItemEnabled(bool b)
+{
+ d->rb_menu->setItemEnabled(d->mPageLayout, b);
+}
+
+/*================================================================*/
+void KoRuler::setMousePos( int mx, int my )
+{
+ if ( !showMPos || ( mx == mposX && my == mposY ) ) return;
+
+ QPainter p( this );
+ p.setRasterOp( Qt::NotROP );
+
+ if ( orientation == Qt::Horizontal ) {
+ if ( hasToDelete )
+ p.drawLine( mposX, 1, mposX, height() - 1 );
+ p.drawLine( mx, 1, mx, height() - 1 );
+ hasToDelete = true;
+ }
+ else {
+ if ( hasToDelete )
+ p.drawLine( 1, mposY, width() - 1, mposY );
+ p.drawLine( 1, my, width() - 1, my );
+ hasToDelete = true;
+ }
+ p.end();
+
+ mposX = mx;
+ mposY = my;
+}
+
+// distance between the main lines (those with a number)
+double KoRuler::lineDistance() const
+{
+ switch( m_unit ) {
+ case KoUnit::U_INCH:
+ return INCH_TO_POINT( m_zoom ); // every inch
+ case KoUnit::U_PT:
+ return 100.0 * m_zoom; // every 100 pt
+ case KoUnit::U_MM:
+ case KoUnit::U_CM:
+ case KoUnit::U_DM:
+ return CM_TO_POINT ( m_zoom ); // every cm
+ case KoUnit::U_PI:
+ return PI_TO_POINT ( 10.0 * m_zoom ); // every 10 pica
+ case KoUnit::U_DD:
+ return DD_TO_POINT( m_zoom ); // every diderot
+ case KoUnit::U_CC:
+ return CC_TO_POINT( 10.0 * m_zoom ); // every 10 cicero
+ }
+ // should never end up here
+ return 100.0 * m_zoom;
+}
+
+/*================================================================*/
+void KoRuler::drawHorizontal( QPainter *_painter )
+{
+ QFont font = KGlobalSettings::toolBarFont();
+ QFontMetrics fm( font );
+ resize( width(), QMAX( fm.height() + 4, 20 ) );
+
+ // Use a double-buffer pixmap
+ QPainter p( &buffer );
+ p.fillRect( 0, 0, width(), height(), QBrush( colorGroup().brush( QColorGroup::Background ) ) );
+
+ int totalw = qRound( zoomIt(d->layout.ptWidth) );
+ QString str;
+
+ p.setBrush( colorGroup().brush( QColorGroup::Base ) );
+
+ // Draw white rect
+ QRect r;
+ if ( !d->whileMovingBorderLeft )
+ r.setLeft( -diffx + frameStart );
+ else
+ r.setLeft( d->oldMx );
+ r.setTop( 0 );
+ if ( !d->whileMovingBorderRight )
+ r.setWidth(d->frameEnd-frameStart);
+ else
+ r.setRight( d->oldMx );
+ r.setBottom( height() );
+
+ p.drawRect( r );
+ p.setFont( font );
+
+ // Draw the numbers
+ double dist = lineDistance();
+ int maxwidth = 0;
+
+ for ( double i = 0.0;i <= (double)totalw;i += dist ) {
+ str = QString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) );
+ int textwidth = fm.width( str );
+ maxwidth = QMAX( maxwidth, textwidth );
+ }
+
+ // Make sure that the ruler stays readable at lower zoom levels
+ while( dist <= maxwidth ) {
+ dist += lineDistance();
+ }
+
+ for ( double i = 0.0;i <= (double)totalw;i += dist ) {
+ str = QString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) );
+ int textwidth = fm.width( str );
+ maxwidth = QMAX( maxwidth, textwidth );
+ p.drawText( qRound(i) - diffx - qRound(textwidth * 0.5),
+ qRound(( height() - fm.height() ) * 0.5),
+ textwidth, height(), AlignLeft | AlignTop, str );
+ }
+
+ // Draw the medium-sized lines
+ // Only if we have enough space (i.e. not at 33%)
+ if ( dist > maxwidth + 2 )
+ {
+ for ( double i = dist * 0.5;i <= (double)totalw;i += dist ) {
+ int ii=qRound(i);
+ p.drawLine( ii - diffx, 7, ii - diffx, height() - 7 );
+ }
+ }
+
+ // Draw the small lines
+ // Only if we have enough space (i.e. not at 33%)
+ if ( dist * 0.5 > maxwidth + 2 )
+ {
+ for ( double i = dist * 0.25;i <= (double)totalw;i += dist * 0.5 ) {
+ int ii=qRound(i);
+ p.drawLine( ii - diffx, 9, ii - diffx, height() - 9 );
+ }
+ }
+
+ // Draw ending bar (at page width)
+ //int constant=zoomIt(1);
+ //p.drawLine( totalw - diffx + constant, 1, totalw - diffx + constant, height() - 1 );
+ //p.setPen( colorGroup().color( QColorGroup::Base ) );
+ //p.drawLine( totalw - diffx, 1, totalw - diffx, height() - 1 );
+
+ // Draw starting bar (at 0)
+ //p.setPen( colorGroup().color( QColorGroup::Text ) );
+ //p.drawLine( -diffx, 1, -diffx, height() - 1 );
+ //p.setPen( colorGroup().color( QColorGroup::Base ) );
+ //p.drawLine( -diffx - constant, 1, -diffx - constant, height() - 1 );
+
+ // Show the mouse position
+ if ( d->action == A_NONE && showMPos ) {
+ p.setPen( colorGroup().color( QColorGroup::Text ) );
+ p.drawLine( mposX, 1, mposX, height() - 1 );
+ }
+ hasToDelete = false;
+
+ p.end();
+ _painter->drawPixmap( 0, 0, buffer );
+}
+
+
+/*================================================================*/
+void KoRuler::drawVertical( QPainter *_painter )
+{
+ QFont font = KGlobalSettings::toolBarFont();
+ QFontMetrics fm( font );
+ resize( QMAX( fm.height() + 4, 20 ), height() );
+
+ QPainter p( &buffer );
+ p.fillRect( 0, 0, width(), height(), QBrush( colorGroup().brush( QColorGroup::Background ) ) );
+
+ int totalh = qRound( zoomIt(d->layout.ptHeight) );
+ // Clip rect - this gives basically always a rect like (2,2,width-2,height-2)
+ QRect paintRect = _painter->clipRegion( QPainter::CoordPainter ).boundingRect();
+ // Ruler rect
+ QRect rulerRect( 0, -diffy, width(), totalh );
+
+ if ( paintRect.intersects( rulerRect ) ) {
+ QString str;
+
+ p.setBrush( colorGroup().brush( QColorGroup::Base ) );
+
+ // Draw white rect
+ QRect r;
+ if ( !d->whileMovingBorderTop )
+ r.setTop( -diffy + frameStart );
+ else
+ r.setTop( d->oldMy );
+ r.setLeft( 0 );
+ if ( !d->whileMovingBorderBottom )
+ r.setHeight(d->frameEnd-frameStart);
+ else
+ r.setBottom( d->oldMy );
+ r.setRight( width() );
+
+ p.drawRect( r );
+ p.setFont( font );
+
+ // Draw the numbers
+ double dist = lineDistance();
+ int maxheight = 0;
+
+ for ( double i = 0.0;i <= (double)totalh;i += dist ) {
+ str = QString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) );
+ int textwidth = fm.width( str );
+ maxheight = QMAX( maxheight, textwidth );
+ }
+
+ // Make sure that the ruler stays readable at lower zoom levels
+ while( dist <= maxheight ) {
+ dist += lineDistance();
+ }
+
+ for ( double i = 0.0;i <= (double)totalh;i += dist ) {
+ str = QString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) );
+ int textheight = fm.height();
+ int textwidth = fm.width( str );
+ maxheight = QMAX( maxheight, textwidth );
+ p.save();
+ p.translate( qRound(( width() - textheight ) * 0.5),
+ qRound(i) - diffy + qRound(textwidth * 0.5) );
+ p.rotate( -90 );
+ p.drawText( 0, 0, textwidth + 1, textheight, AlignLeft | AlignTop, str );
+ p.restore();
+ }
+
+ // Draw the medium-sized lines
+ if ( dist > maxheight + 2 )
+ {
+ for ( double i = dist * 0.5;i <= (double)totalh;i += dist ) {
+ int ii=qRound(i);
+ p.drawLine( 7, ii - diffy, width() - 7, ii - diffy );
+ }
+ }
+
+ // Draw the small lines
+ if ( dist * 0.5 > maxheight + 2 )
+ {
+ for ( double i = dist * 0.25;i <=(double)totalh;i += dist *0.5 ) {
+ int ii=qRound(i);
+ p.drawLine( 9, ii - diffy, width() - 9, ii - diffy );
+ }
+ }
+
+ // Draw ending bar (at page height)
+ //p.drawLine( 1, totalh - diffy + 1, width() - 1, totalh - diffy + 1 );
+ //p.setPen( colorGroup().color( QColorGroup::Base ) );
+ //p.drawLine( 1, totalh - diffy, width() - 1, totalh - diffy );
+
+ // Draw starting bar (at 0)
+ //p.setPen( colorGroup().color( QColorGroup::Text ) );
+ //p.drawLine( 1, -diffy, width() - 1, -diffy );
+ //p.setPen( colorGroup().color( QColorGroup::Base ) );
+ //p.drawLine( 1, -diffy - 1, width() - 1, -diffy - 1 );
+ }
+
+ // Show the mouse position
+ if ( d->action == A_NONE && showMPos ) {
+ p.setPen( colorGroup().color( QColorGroup::Text ) );
+ p.drawLine( 1, mposY, width() - 1, mposY );
+ }
+ hasToDelete = false;
+
+ p.end();
+ _painter->drawPixmap( 0, 0, buffer );
+}
+
+void KoRuler::mousePressEvent( QMouseEvent *e )
+{
+ if( !d->m_bReadWrite)
+ return;
+
+ d->oldMx = e->x();
+ d->oldMy = e->y();
+ d->mousePressed = true;
+ d->removeTab.type = T_INVALID;
+
+ switch ( e->button() ) {
+ case RightButton:
+ if(d->currTab.type == T_INVALID || !(d->flags & F_TABS))
+ d->rb_menu->setItemEnabled(d->mRemoveTab, false);
+ else
+ d->rb_menu->setItemEnabled(d->mRemoveTab, true);
+ d->rb_menu->popup( QCursor::pos() );
+ d->action = A_NONE;
+ d->mousePressed = false;
+ return;
+ case MidButton:
+ // MMB shall do like double-click (it opens a dialog).
+ handleDoubleClick();
+ return;
+ case LeftButton:
+ if ( d->action == A_BR_RIGHT || d->action == A_BR_LEFT ) {
+ if ( d->action == A_BR_RIGHT )
+ d->whileMovingBorderRight = true;
+ else
+ d->whileMovingBorderLeft = true;
+
+ if ( d->canvas )
+ drawLine(d->oldMx, -1);
+ update();
+ } else if ( d->action == A_BR_TOP || d->action == A_BR_BOTTOM ) {
+ if ( d->action == A_BR_TOP )
+ d->whileMovingBorderTop = true;
+ else
+ d->whileMovingBorderBottom = true;
+
+ if ( d->canvas ) {
+ QPainter p( d->canvas );
+ p.setRasterOp( Qt::NotROP );
+ p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy );
+ p.end();
+ }
+ update();
+ } else if ( d->action == A_FIRST_INDENT || d->action == A_LEFT_INDENT || d->action == A_RIGHT_INDENT ) {
+ if ( d->canvas )
+ drawLine(d->oldMx, -1);
+ } else if ( d->action == A_TAB ) {
+ if ( d->canvas && d->currTab.type != T_INVALID ) {
+ drawLine( qRound( applyRtlAndZoom(d->currTab.ptPos) ) + frameStart - diffx, -1 );
+ }
+ }
+ else if ( d->flags & F_HELPLINES )
+ {
+ setCursor( orientation == Qt::Horizontal ?
+ Qt::sizeVerCursor : Qt::sizeHorCursor );
+ d->action = A_HELPLINES;
+ }
+ default:
+ break;
+ }
+}
+
+void KoRuler::mouseReleaseEvent( QMouseEvent *e )
+{
+ d->mousePressed = false;
+
+ // Hacky, but necessary to prevent multiple tabs with the same coordinates (Werner)
+ bool fakeMovement=false;
+ if(d->removeTab.type != T_INVALID) {
+ mouseMoveEvent(e);
+ fakeMovement=true;
+ }
+
+ if ( d->action == A_BR_RIGHT || d->action == A_BR_LEFT ) {
+ d->whileMovingBorderRight = false;
+ d->whileMovingBorderLeft = false;
+
+ if ( d->canvas )
+ drawLine(d->oldMx, -1);
+ update();
+ emit newPageLayout( d->layout );
+ } else if ( d->action == A_BR_TOP || d->action == A_BR_BOTTOM ) {
+ d->whileMovingBorderTop = false;
+ d->whileMovingBorderBottom = false;
+
+ if ( d->canvas ) {
+ QPainter p( d->canvas );
+ p.setRasterOp( Qt::NotROP );
+ p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy );
+ p.end();
+ }
+ update();
+ emit newPageLayout( d->layout );
+ } else if ( d->action == A_FIRST_INDENT ) {
+ if ( d->canvas )
+ drawLine(d->oldMx, -1);
+ update();
+ emit newFirstIndent( i_first );
+ } else if ( d->action == A_LEFT_INDENT ) {
+ if ( d->canvas )
+ drawLine(d->oldMx, -1);
+ update();
+ emit newLeftIndent( i_left );
+ } else if ( d->action == A_RIGHT_INDENT ) {
+ if ( d->canvas )
+ drawLine(d->oldMx, -1);
+ update();
+ emit newRightIndent( d->i_right );
+ } else if ( d->action == A_TAB ) {
+ if ( d->canvas && !fakeMovement ) {
+ drawLine( qRound( applyRtlAndZoom( d->currTab.ptPos ) ) + frameStart - diffx, -1);
+ }
+ if ( willRemoveTab( e->y() ) )
+ {
+ d->tabList.remove(d->currTab);
+ }
+ qHeapSort( d->tabList );
+
+ // Delete the new tabulator if it is placed on top of another.
+ KoTabulatorList::ConstIterator tmpTab=d->tabList.begin();
+ int count=0;
+ while(tmpTab!=d->tabList.end()) {
+ if( equals( (*tmpTab).ptPos, d->currTab.ptPos ) ) {
+ count++;
+ if(count > 1) {
+ d->tabList.remove(d->currTab);
+ break;
+ }
+ }
+ tmpTab++;
+ }
+ //searchTab( e->x() ); // DF: why set currTab here?
+ emit tabListChanged( d->tabList );
+ update();
+ }
+ else if( d->action == A_HELPLINES )
+ {
+ emit addHelpline( e->pos(), orientation == Qt::Horizontal);
+ setCursor( ArrowCursor );
+ }
+ d->currTab.type = T_INVALID; // added (DF)
+}
+
+void KoRuler::mouseMoveEvent( QMouseEvent *e )
+{
+ hasToDelete = false;
+
+ int pw = d->frameEnd - frameStart;
+ int ph = qRound(zoomIt(d->layout.ptHeight));
+ int left = frameStart - diffx;
+ int top = qRound(zoomIt(d->layout.ptTop));
+ top -= diffy;
+ int right = d->frameEnd - diffx;
+ int bottom = qRound(zoomIt(d->layout.ptBottom));
+ bottom = ph - bottom - diffy;
+ // Cumulate first-line-indent
+ int ip_first = qRound( zoomIt( i_first + ( d->rtl ? d->i_right : i_left) ) );
+ int ip_left = qRound(zoomIt(i_left));
+ int ip_right = qRound(zoomIt(d->i_right));
+
+ int mx = e->x();
+ mx = mx+diffx < 0 ? 0 : mx;
+ int my = e->y();
+ my = my+diffy < 0 ? 0 : my;
+
+ QToolTip::remove( this);
+ switch ( orientation ) {
+ case Qt::Horizontal: {
+ if ( !d->mousePressed ) {
+ setCursor( ArrowCursor );
+ d->action = A_NONE;
+ /////// ######
+ // At the moment, moving the left and right border indicators
+ // is disabled when setFrameStartEnd has been called (i.e. in KWord)
+ // Changing the layout margins directly from it would be utterly wrong
+ // (just try the 2-columns modes...). What needs to be done is:
+ // emitting a signal frameResized in mouseReleaseEvent, when a left/right
+ // border has been moved, and in kword we need to update the margins from
+ // there, if the left border of the 1st column or the right border of the
+ // last column was moved... and find what to do with the other borders.
+ // And for normal frames, resize the frame without touching the page layout.
+ // All that is too much for now -> disabling.
+ if ( !m_bFrameStartSet )
+ {
+ if ( mx > left - 5 && mx < left + 5 ) {
+ setCursor( Qt::sizeHorCursor );
+ d->action = A_BR_LEFT;
+ } else if ( mx > right - 5 && mx < right + 5 ) {
+ setCursor( Qt::sizeHorCursor );
+ d->action = A_BR_RIGHT;
+ }
+ }
+ if ( d->flags & F_TABS )
+ searchTab(mx);
+ } else {
+ // Calculate the new value.
+ int newPos=mx;
+ if( newPos!=right && gridSize!=0.0 && (e->state() & ShiftButton)==0) { // apply grid.
+ double grid=zoomIt(gridSize * 16);
+ newPos=qRound( ((newPos * 16 / grid) * grid) / 16 );
+ }
+ if(newPos-left < 0) newPos=left;
+ else if (right-newPos < 0) newPos=right;
+ double newValue = unZoomIt(static_cast<double>(newPos) - frameStart + diffx);
+
+ switch ( d->action ) {
+ case A_BR_LEFT: {
+ if ( d->canvas && mx < right-10 && mx+diffx-2 > 0) {
+ drawLine( d->oldMx, mx );
+ d->layout.ptLeft = unZoomIt(static_cast<double>(mx + diffx));
+ if( ip_left > right-left-15 ) {
+ ip_left=right-left-15;
+ ip_left=ip_left<0 ? 0 : ip_left;
+ i_left=unZoomIt( ip_left );
+ emit newLeftIndent( i_left );
+ }
+ if ( ip_right > right-left-15 ) {
+ ip_right=right-left-15;
+ ip_right=ip_right<0? 0 : ip_right;
+ d->i_right=unZoomIt( ip_right );
+ emit newRightIndent( d->i_right );
+ }
+ d->oldMx = mx;
+ d->oldMy = my;
+ update();
+ }
+ else
+ return;
+ } break;
+ case A_BR_RIGHT: {
+ if ( d->canvas && mx > left+10 && mx+diffx <= pw-2) {
+ drawLine( d->oldMx, mx );
+ d->layout.ptRight = unZoomIt(static_cast<double>(pw - ( mx + diffx )));
+ if( ip_left > right-left-15 ) {
+ ip_left=right-left-15;
+ ip_left=ip_left<0 ? 0 : ip_left;
+ i_left=unZoomIt( ip_left );
+ emit newLeftIndent( i_left );
+ }
+ if ( ip_right > right-left-15 ) {
+ ip_right=right-left-15;
+ ip_right=ip_right<0? 0 : ip_right;
+ d->i_right=unZoomIt( ip_right );
+ emit newRightIndent( d->i_right );
+ }
+ d->oldMx = mx;
+ d->oldMy = my;
+ update();
+ }
+ else
+ return;
+ } break;
+ case A_FIRST_INDENT: {
+ if ( d->canvas ) {
+ if (d->rtl)
+ newValue = unZoomIt(pw) - newValue - d->i_right;
+ else
+ newValue -= i_left;
+ if(newValue == i_first) break;
+ drawLine( d->oldMx, newPos);
+ d->oldMx=newPos;
+ i_first = newValue;
+ update();
+ }
+ } break;
+ case A_LEFT_INDENT: {
+ if ( d->canvas ) {
+ //if (d->rtl) newValue = unZoomIt(pw) - newValue;
+ if(newValue == i_left) break;
+
+ drawLine( d->oldMx, newPos);
+ i_left = newValue;
+ d->oldMx = newPos;
+ update();
+ }
+ } break;
+ case A_RIGHT_INDENT: {
+ if ( d->canvas ) {
+ double rightValue = unZoomIt(right - newPos);
+ //if (d->rtl) rightValue = unZoomIt(pw) - rightValue;
+ if(rightValue == d->i_right) break;
+
+ drawLine( d->oldMx, newPos);
+ d->i_right=rightValue;
+ d->oldMx = newPos;
+ update();
+ }
+ } break;
+ case A_TAB: {
+ if ( d->canvas) {
+ if (d->rtl) newValue = unZoomIt(pw) - newValue;
+ if(newValue == d->currTab.ptPos) break; // no change
+ QPainter p( d->canvas );
+ p.setRasterOp( Qt::NotROP );
+ // prevent 1st drawLine when we just created a new tab
+ // (it's a NOT line)
+ double pt;
+ int pt_fr;
+ if( d->currTab != d->removeTab )
+ {
+ pt = applyRtlAndZoom(d->currTab.ptPos);
+ pt_fr = qRound(pt) + frameStart - diffx;
+ p.drawLine( pt_fr, 0, pt_fr, d->canvas->height() );
+ }
+
+ KoTabulatorList::Iterator it = d->tabList.find( d->currTab );
+ Q_ASSERT( it != d->tabList.end() );
+ if ( it != d->tabList.end() )
+ (*it).ptPos = newValue;
+ d->currTab.ptPos = newValue;
+
+ pt = applyRtlAndZoom( newValue );
+ pt_fr = qRound(pt) + frameStart - diffx;
+ p.drawLine( pt_fr, 0, pt_fr, d->canvas->height() );
+
+ p.end();
+ d->oldMx = mx;
+ d->oldMy = my;
+ d->removeTab.type = T_INVALID;
+ update();
+ }
+ } break;
+ default: break;
+ }
+ }
+ if( d->action == A_HELPLINES )
+ {
+ emit moveHelpLines( e->pos(), orientation == Qt::Horizontal);
+ }
+
+ return;
+ } break;
+ case Qt::Vertical: {
+ if ( !d->mousePressed ) {
+ setCursor( ArrowCursor );
+ d->action = A_NONE;
+ if ( d->flags & F_NORESIZE )
+ break;
+ if ( my > top - 5 && my < top + 5 ) {
+ QToolTip::add( this, i18n("Top margin") );
+ setCursor( Qt::sizeVerCursor );
+ d->action = A_BR_TOP;
+ } else if ( my > bottom - 5 && my < bottom + 5 ) {
+ QToolTip::add( this, i18n("Bottom margin") );
+ setCursor( Qt::sizeVerCursor );
+ d->action = A_BR_BOTTOM;
+ }
+ } else {
+ switch ( d->action ) {
+ case A_BR_TOP: {
+ if ( d->canvas && my < bottom-20 && my+diffy-2 > 0) {
+ QPainter p( d->canvas );
+ p.setRasterOp( Qt::NotROP );
+ p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy );
+ p.drawLine( 0, my, d->canvas->width(), my );
+ p.end();
+ d->layout.ptTop = unZoomIt(static_cast<double>(my + diffy));
+ d->oldMx = mx;
+ d->oldMy = my;
+ update();
+ }
+ else
+ return;
+ } break;
+ case A_BR_BOTTOM: {
+ if ( d->canvas && my > top+20 && my+diffy < ph-2) {
+ QPainter p( d->canvas );
+ p.setRasterOp( Qt::NotROP );
+ p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy );
+ p.drawLine( 0, my, d->canvas->width(), my );
+ p.end();
+ d->layout.ptBottom = unZoomIt(static_cast<double>(ph - ( my + diffy )));
+ d->oldMx = mx;
+ d->oldMy = my;
+ update();
+ }
+ else
+ return;
+ } break;
+ default: break;
+ }
+ }
+ } break;
+ }
+ if( d->action == A_HELPLINES )
+ {
+ emit moveHelpLines( e->pos(), orientation == Qt::Horizontal);
+ }
+
+ d->oldMx = mx;
+ d->oldMy = my;
+}
+
+void KoRuler::resizeEvent( QResizeEvent *e )
+{
+ QFrame::resizeEvent( e );
+ buffer.resize( size() );
+}
+
+void KoRuler::mouseDoubleClickEvent( QMouseEvent* )
+{
+ handleDoubleClick();
+}
+
+void KoRuler::handleDoubleClick()
+{
+ if ( !d->m_bReadWrite )
+ return;
+
+ d->doubleClickedIndent = false;
+
+ // When Binary Compatibility is broken this will hopefully emit a
+ // doubleClicked(int) to differentiate between double-clicking an
+ // indent and double-clicking the ruler
+ if ( d->flags & F_INDENTS ) {
+ if ( d->action == A_LEFT_INDENT || d->action == A_RIGHT_INDENT || d->action == A_FIRST_INDENT ) {
+ d->doubleClickedIndent = true;
+ emit doubleClicked(); // usually paragraph dialog
+ return;
+ }
+ }
+
+ // Double-clicked nothing
+ d->action = A_NONE;
+ emit doubleClicked(); // usually page layout dialog
+}
+
+void KoRuler::setTabList( const KoTabulatorList & _tabList )
+{
+ d->tabList = _tabList;
+ qHeapSort(d->tabList); // "Trust no one." as opposed to "In David we trust."
+
+ // Note that d->currTab and d->removeTab could now point to
+ // tabs which don't exist in d->tabList
+
+ update();
+}
+
+double KoRuler::makeIntern( double _v )
+{
+ return KoUnit::fromUserValue( _v, m_unit );
+}
+
+void KoRuler::setupMenu()
+{
+ d->rb_menu = new QPopupMenu();
+ Q_CHECK_PTR( d->rb_menu );
+ for ( uint i = 0 ; i <= KoUnit::U_LASTUNIT ; ++i )
+ {
+ KoUnit::Unit unit = static_cast<KoUnit::Unit>( i );
+ d->rb_menu->insertItem( KoUnit::unitDescription( unit ), i /*as id*/ );
+ if ( m_unit == unit )
+ d->rb_menu->setItemChecked( i, true );
+ }
+ connect( d->rb_menu, SIGNAL( activated( int ) ), SLOT( slotMenuActivated( int ) ) );
+
+ d->rb_menu->insertSeparator();
+ d->mPageLayout=d->rb_menu->insertItem(i18n("Page Layout..."), this, SLOT(pageLayoutDia()));
+#if 0
+ d->rb_menu->insertSeparator();
+ d->mRemoveTab=d->rb_menu->insertItem(i18n("Remove Tabulator"), this, SLOT(rbRemoveTab()));
+ d->rb_menu->setItemEnabled( d->mRemoveTab, false );
+#endif
+}
+
+void KoRuler::uncheckMenu()
+{
+ for ( uint i = 0 ; i <= KoUnit::U_LASTUNIT ; ++i )
+ d->rb_menu->setItemChecked( i, false );
+}
+
+void KoRuler::setUnit( KoUnit::Unit unit )
+{
+ m_unit = unit;
+ uncheckMenu();
+ d->rb_menu->setItemChecked( m_unit, true );
+ update();
+}
+
+void KoRuler::setZoom( const double& zoom )
+{
+ if(zoom==m_zoom)
+ return;
+ if(zoom < 1E-4) // Don't do 0 or negative values
+ return;
+ m_zoom=zoom;
+ m_1_zoom=1/m_zoom;
+ update();
+}
+
+bool KoRuler::willRemoveTab( int y ) const
+{
+ return (y < -50 || y > height() + 25) && d->currTab.type != T_INVALID;
+}
+
+void KoRuler::rbRemoveTab() {
+
+ d->tabList.remove( d->currTab );
+ d->currTab.type = T_INVALID;
+ emit tabListChanged( d->tabList );
+ update();
+}
+
+void KoRuler::setReadWrite(bool _readWrite)
+{
+ d->m_bReadWrite=_readWrite;
+}
+
+void KoRuler::searchTab(int mx) {
+
+ int pos;
+ d->currTab.type = T_INVALID;
+ KoTabulatorList::ConstIterator it = d->tabList.begin();
+ for ( ; it != d->tabList.end() ; ++it ) {
+ pos = qRound(applyRtlAndZoom((*it).ptPos)) - diffx + frameStart;
+ if ( mx > pos - 5 && mx < pos + 5 ) {
+ setCursor( Qt::sizeHorCursor );
+ d->action = A_TAB;
+ d->currTab = *it;
+ break;
+ }
+ }
+}
+
+void KoRuler::drawLine(int oldX, int newX) {
+
+ QPainter p( d->canvas );
+ p.setRasterOp( Qt::NotROP );
+ p.drawLine( oldX, 0, oldX, d->canvas->height() );
+ if(newX!=-1)
+ p.drawLine( newX, 0, newX, d->canvas->height() );
+ p.end();
+}
+
+void KoRuler::showMousePos( bool _showMPos )
+{
+ showMPos = _showMPos;
+ hasToDelete = false;
+ mposX = -1;
+ mposY = -1;
+ update();
+}
+
+void KoRuler::setOffset( int _diffx, int _diffy )
+{
+ //kdDebug() << "KoRuler::setOffset " << _diffx << "," << _diffy << endl;
+ diffx = _diffx;
+ diffy = _diffy;
+ update();
+}
+
+void KoRuler::setFrameStartEnd( int _frameStart, int _frameEnd )
+{
+ if ( _frameStart != frameStart || _frameEnd != d->frameEnd || !m_bFrameStartSet )
+ {
+ frameStart = _frameStart;
+ d->frameEnd = _frameEnd;
+ // Remember that setFrameStartEnd was called. This activates a slightly
+ // different mode (when moving start and end positions).
+ m_bFrameStartSet = true;
+ update();
+ }
+}
+
+void KoRuler::setRightIndent( double _right )
+{
+ d->i_right = makeIntern( _right );
+ update();
+}
+
+void KoRuler::setDirection( bool rtl )
+{
+ d->rtl = rtl;
+ update();
+}
+
+void KoRuler::changeFlags(int _flags)
+{
+ d->flags = _flags;
+}
+
+int KoRuler::flags() const
+{
+ return d->flags;
+}
+
+bool KoRuler::doubleClickedIndent() const
+{
+ return d->doubleClickedIndent;
+}
+
+double KoRuler::applyRtlAndZoom( double value ) const
+{
+ int frameWidth = d->frameEnd - frameStart;
+ return d->rtl ? ( frameWidth - zoomIt( value ) ) : zoomIt( value );
+}
+
+double KoRuler::unZoomItRtl( int pixValue ) const
+{
+ int frameWidth = d->frameEnd - frameStart;
+ return d->rtl ? ( unZoomIt( (double)(frameWidth - pixValue) ) ) : unZoomIt( (double)pixValue );
+}
+
+void KoRuler::slotMenuActivated( int i )
+{
+ if ( i >= 0 && i <= KoUnit::U_LASTUNIT )
+ {
+ KoUnit::Unit unit = static_cast<KoUnit::Unit>(i);
+ setUnit( unit );
+ emit unitChanged( unit );
+ }
+}
+
+QSize KoRuler::minimumSizeHint() const
+{
+ QSize size;
+ QFont font = KGlobalSettings::toolBarFont();
+ QFontMetrics fm( font );
+
+ size.setWidth( QMAX( fm.height() + 4, 20 ) );
+ size.setHeight( QMAX( fm.height() + 4, 20 ) );
+
+ return size;
+}
+
+QSize KoRuler::sizeHint() const
+{
+ return minimumSizeHint();
+}
+
+void KoRuler::setPageLayout( const KoPageLayout& _layout )
+{
+ d->layout = _layout;
+ update();
+}
+
+#include "kreruler.moc"
diff --git a/krecipes/src/widgets/kreruler.h b/krecipes/src/widgets/kreruler.h
new file mode 100644
index 0000000..b313e02
--- /dev/null
+++ b/krecipes/src/widgets/kreruler.h
@@ -0,0 +1,366 @@
+/* This file is part of the KDE project
+ Copyright (C) 1998, 1999 Reginald Stadlbauer <reggie@kde.org>
+ Copyright (C) 2005 Jason Kivlighn <jkivlighn@gmail.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+// Description: Ruler (header)
+
+/******************************************************************/
+
+#ifndef KRERULER_H
+#define KRERULER_H
+
+#include <qframe.h>
+#include <qpixmap.h>
+
+#include <kdemacros.h>
+
+#if 0
+#include <koGlobal.h>
+#include <koTabChooser.h>
+#endif
+
+#include "kreunit.h"
+
+class KoPageLayout;
+class QPainter;
+
+enum KoTabulators { T_LEFT = 0, T_CENTER = 1, T_RIGHT = 2, T_DEC_PNT = 3, T_INVALID = -1 };
+enum KoTabulatorFilling { TF_BLANK = 0, TF_DOTS = 1, TF_LINE = 2, TF_DASH = 3, TF_DASH_DOT = 4, TF_DASH_DOT_DOT = 5};
+
+/**
+ * Struct: KoTabulator
+ * Defines the position of a tabulation (in pt), and its type
+ */
+struct KoTabulator {
+ /**
+ * Position of the tab in pt
+ */
+ double ptPos;
+ /**
+ * Type of tab (left/center/right/decimalpoint)
+ */
+ KoTabulators type;
+ /**
+ * Type of tab filling.
+ */
+ KoTabulatorFilling filling;
+ /**
+ * Width of the tab filling line.
+ */
+ double ptWidth;
+ /**
+ * Alignment character.
+ */
+ QChar alignChar;
+
+ bool operator==( const KoTabulator & t ) const {
+ return QABS( ptPos - t.ptPos ) < 1E-4 && type == t.type &&
+ filling == t.filling && QABS( ptWidth - t.ptWidth ) < 1E-4;
+ }
+ bool operator!=( const KoTabulator & t ) const {
+ return !operator==(t);
+ }
+ // Operators used for sorting
+ bool operator < ( const KoTabulator & t ) const {
+ return ptPos < t.ptPos;
+ }
+ bool operator <= ( const KoTabulator & t ) const {
+ return ptPos <= t.ptPos;
+ }
+ bool operator > ( const KoTabulator & t ) const {
+ return ptPos > t.ptPos;
+ }
+};
+
+typedef QValueList<KoTabulator> KoTabulatorList;
+
+class KoRulerPrivate;
+
+/**
+ * KoRuler is the horizontal or vertical ruler, to be used around
+ * the drawing area of most KOffice programs.
+ *
+ * It shows the graduated ruler with numbering, in any of the base units (like mm/pt/inch),
+ * and supports zooming, tabulators, paragraph indents, showing the mouse position, etc.
+ *
+ * It also offers a popupmenu upon right-clicking, for changing the unit,
+ * the page layout, or removing a tab.
+ */
+class KoRuler : public QFrame
+{
+ Q_OBJECT
+ friend class KoRulerPrivate; // for the Action enum
+public:
+ static const int F_TABS;
+ static const int F_INDENTS;
+ static const int F_HELPLINES;
+ static const int F_NORESIZE;
+
+ /**
+ * Create a ruler
+ * TODO document params
+ */
+ KoRuler( QWidget *_parent, QWidget *_canvas, Orientation _orientation,
+ const KoPageLayout& _layout, int _flags, KoUnit::Unit _unit );
+ ~KoRuler();
+
+ /**
+ * Set the unit to be used.
+ */
+ void setUnit( KoUnit::Unit unit );
+
+ /**
+ * Set the zoom of the ruler (default value of 1.0 is 100%)
+ */
+ void setZoom( const double& zoom=1.0 );
+ /**
+ * @return the current zoom level
+ */
+ const double& zoom() const { return m_zoom; }
+
+ /**
+ * Set the page layout, see @ref KoPageLayout.
+ * This defines the size of the page and the margins,
+ * from which the size of the ruler is deducted.
+ */
+ void setPageLayout( const KoPageLayout& _layout );
+
+ /**
+ * Call showMousePos(true) if the ruler should indicate the position
+ * of the mouse. This is usually only the case for drawing applications,
+ * so it is not enabled by default.
+ */
+ void showMousePos( bool _showMPos );
+ /**
+ * Set the position of the mouse, to update the indication in the ruler.
+ * This is only effective if showMousePos(true) was called previously.
+ * The position to give is not zoomed, it's in real pixel coordinates!
+ */
+ void setMousePos( int mx, int my );
+
+ /**
+ * Set a global offset to the X and Y coordinates.
+ * Usually the main drawing area is a QScrollView, and this is called
+ * with contentsX() and contentsY(), each time those values change.
+ */
+ void setOffset( int _diffx, int _diffy );
+
+ /**
+ * Set the [paragraph] left indent to the specified position (in the current unit)
+ */
+ void setLeftIndent( double _left )
+ { i_left = makeIntern( _left ); update(); }
+
+ /**
+ * Set the [paragraph] first-line left indent to the specified position (in the current unit)
+ * This indent is cumulated with the left or right margin, depending on the [paragraph] direction.
+ */
+ void setFirstIndent( double _first )
+ { i_first = makeIntern( _first ); update(); }
+
+ /**
+ * Set the [paragraph] right indent to the specified position (in the current unit)
+ */
+ void setRightIndent( double _right );
+
+ /**
+ * Set the [paragraph] direction. By default (rtl=false), the left indent is on the
+ * left, and the right indent is on the right ;)
+ * If rtl=true, it's the opposite.
+ */
+ void setDirection( bool rtl );
+
+ /**
+ * Set the list of tabulators to show in the ruler.
+ */
+ void setTabList( const KoTabulatorList & tabList );
+
+ /**
+ * Set the start and the end of the current 'frame', i.e. the part
+ * of the page in which we are currently working. See KWord frames
+ * for an example where this is used. The tab positions and paragraph
+ * indents then become relative to the beginning of the frame, and the
+ * ruler goes from frameStart to frameEnd instead of using the page margins.
+ * @p _frameStart et @p _frameEnd are in pixel coordinates.
+ */
+ void setFrameStartEnd( int _frameStart, int _frameEnd );
+
+ /**
+ * KoRuler is in "read write" mode by default.
+ * Use setReadWrite(false) to use it in read-only mode.
+ */
+ void setReadWrite( bool _readWrite );
+
+ /**
+ * Change the flag (i.e. activate or deactivate certain features of KoRuler)
+ */
+ void changeFlags(int _flags);
+
+ /**
+ * Set the size of the grid used for tabs positioning, size in pt.
+ * default value is 0. 0 means no grid.
+ */
+ void setGridSize(double newGridSize) { gridSize=newGridSize; }
+
+ /**
+ * @return the current flags
+ */
+ int flags() const;
+
+ /**
+ * @return whether the current doubleClicked() signal was triggered
+ * by the user double-clicking on an indent (BCI). It returns false
+ * if the user just double-clicked on an "empty" part of the ruler.
+ *
+ * This method is strictly provided for use in a slot connected to the
+ * doubleClicked() signal; calling it at any other time results in
+ * undefined behavior.
+ */
+ bool doubleClickedIndent() const;
+
+ /**
+ * Enable or disable the "Page Layout" menu item.
+ */
+ void setPageLayoutMenuItemEnabled(bool b);
+
+ /**
+ * Reimplemented from QWidget
+ */
+ virtual QSize minimumSizeHint() const;
+
+ /**
+ * Reimplemented from QWidget
+ */
+ virtual QSize sizeHint() const;
+
+signals:
+ void newPageLayout( const KoPageLayout & );
+ void newLeftIndent( double );
+ void newFirstIndent( double );
+ void newRightIndent( double );
+ /** Old signal, kept for compatibility. Use doubleClicked instead. */
+ void openPageLayoutDia();
+ /** This signal is emitted when double-clicking the ruler (or an indent) */
+ void doubleClicked();
+ /** This signal is emitted when double-clicking a tab */
+ void doubleClicked( double ptPos );
+
+ void tabListChanged( const KoTabulatorList & );
+ void unitChanged( KoUnit::Unit );
+
+ void addHelpline(const QPoint &, bool );
+ void moveHelpLines( const QPoint &, bool );
+
+protected:
+ enum Action {A_NONE, A_BR_LEFT, A_BR_RIGHT, A_BR_TOP, A_BR_BOTTOM,
+ A_LEFT_INDENT, A_FIRST_INDENT, A_TAB, A_RIGHT_INDENT,
+ A_HELPLINES };
+
+ void drawContents( QPainter *_painter )
+ { orientation == Qt::Horizontal ? drawHorizontal( _painter ) : drawVertical( _painter ); }
+
+ void drawHorizontal( QPainter *_painter );
+ void drawVertical( QPainter *_painter );
+
+ void mousePressEvent( QMouseEvent *e );
+ void mouseReleaseEvent( QMouseEvent *e );
+ void mouseMoveEvent( QMouseEvent *e );
+ void mouseDoubleClickEvent( QMouseEvent* );
+ void resizeEvent( QResizeEvent *e );
+ void handleDoubleClick();
+
+ double makeIntern( double _v );
+ double zoomIt(const double &value) const;
+ int zoomIt(const int &value) const;
+ unsigned int zoomIt(const unsigned int &value) const;
+ double unZoomIt(const double &value) const;
+ int unZoomIt(const int &value) const;
+ unsigned int unZoomIt(const unsigned int &value) const;
+ void setupMenu();
+ void uncheckMenu();
+ void searchTab(int mx);
+ void drawLine(int oldX, int newX);
+
+private:
+ double applyRtlAndZoom( double value ) const;
+ double unZoomItRtl( int pixValue ) const;
+ double lineDistance() const;
+ bool willRemoveTab( int y ) const;
+
+ KoRulerPrivate *d;
+
+ Qt::Orientation orientation;
+ int diffx, diffy;
+ double i_left, i_first;
+ QPixmap buffer;
+ double m_zoom, m_1_zoom;
+ KoUnit::Unit m_unit;
+ bool hasToDelete;
+ bool showMPos;
+ bool m_bFrameStartSet;
+ bool m_bReadWrite;
+ int mposX, mposY;
+ int frameStart;
+
+ double gridSize;
+
+protected slots:
+ void slotMenuActivated( int i );
+ void pageLayoutDia() { emit doubleClicked()/*openPageLayoutDia()*/; }
+ void rbRemoveTab();
+
+};
+
+inline double KoRuler::zoomIt(const double &value) const {
+ if (m_zoom==1.0)
+ return value;
+ return m_zoom*value;
+}
+
+inline int KoRuler::zoomIt(const int &value) const {
+ if (m_zoom==1.0)
+ return value;
+ return qRound(m_zoom*value);
+}
+
+inline unsigned int KoRuler::zoomIt(const unsigned int &value) const {
+ if (m_zoom==1.0)
+ return value;
+ return static_cast<unsigned int>(qRound(m_zoom*value));
+}
+
+inline double KoRuler::unZoomIt(const double &value) const {
+ if(m_zoom==1.0)
+ return value;
+ return value*m_1_zoom;
+}
+
+inline int KoRuler::unZoomIt(const int &value) const {
+ if(m_zoom==1.0)
+ return value;
+ return qRound(value*m_1_zoom);
+}
+
+inline unsigned int KoRuler::unZoomIt(const unsigned int &value) const {
+ if(m_zoom==1.0)
+ return value;
+ return static_cast<unsigned int>(qRound(value*m_1_zoom));
+}
+
+#endif
diff --git a/krecipes/src/widgets/kretextedit.cpp b/krecipes/src/widgets/kretextedit.cpp
new file mode 100644
index 0000000..55565d2
--- /dev/null
+++ b/krecipes/src/widgets/kretextedit.cpp
@@ -0,0 +1,183 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "kretextedit.h"
+
+#include <qtextstream.h>
+
+#include <kaccel.h>
+#include <kdebug.h>
+
+KreTextEdit::KreTextEdit( QWidget *parent ) : KTextEdit( parent ), KCompletionBase()
+{
+ KCompletion * comp = completionObject(); //creates the completion object
+ comp->setIgnoreCase( true );
+
+ completing = false;
+
+ connect( this, SIGNAL( clicked( int, int ) ), SLOT( haltCompletion() ) );
+}
+
+void KreTextEdit::haltCompletion()
+{
+ completing = false;
+}
+
+void KreTextEdit::keyPressEvent( QKeyEvent *e )
+{
+ // Filter key-events if completion mode is not set to CompletionNone
+ KKey key( e );
+
+ KeyBindingMap keys = getKeyBindings();
+ KShortcut cut;
+ bool noModifier = ( e->state() == NoButton || e->state() == ShiftButton );
+
+ if ( noModifier ) {
+ QString keycode = e->text();
+ if ( !keycode.isEmpty() && keycode.unicode() ->isPrint() ) {
+ QTextEdit::keyPressEvent ( e );
+ tryCompletion();
+ e->accept();
+ return ;
+ }
+ }
+
+ // Handles completion
+ if ( keys[ TextCompletion ].isNull() )
+ cut = KStdAccel::shortcut( KStdAccel::TextCompletion );
+ else
+ cut = keys[ TextCompletion ];
+
+ //using just the standard Ctrl+E isn't user-friendly enough for Grandma...
+ if ( completing && ( cut.contains( key ) || e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return ) ) {
+ int paraFrom, indexFrom, paraTo, indexTo;
+ getSelection ( &paraFrom, &indexFrom, &paraTo, &indexTo );
+
+ removeSelection();
+ setCursorPosition( paraTo, indexTo );
+
+ completing = false;
+ return ;
+ }
+
+ // handle rotation
+
+ // Handles previous match
+ if ( keys[ PrevCompletionMatch ].isNull() )
+ cut = KStdAccel::shortcut( KStdAccel::PrevCompletion );
+ else
+ cut = keys[ PrevCompletionMatch ];
+
+ if ( cut.contains( key ) ) {
+ rotateText( KCompletionBase::PrevCompletionMatch );
+ return ;
+ }
+
+ // Handles next match
+ if ( keys[ NextCompletionMatch ].isNull() )
+ cut = KStdAccel::shortcut( KStdAccel::NextCompletion );
+ else
+ cut = keys[ NextCompletionMatch ];
+
+ if ( cut.contains( key ) ) {
+ rotateText( KCompletionBase::NextCompletionMatch );
+ return ;
+ }
+
+ //any other key events will end any text completion execpt for modifiers
+ switch ( e->key() ) {
+ case Qt::Key_Shift:
+ case Qt::Key_Control:
+ case Qt::Key_Alt:
+ case Qt::Key_Meta:
+ break;
+ default:
+ completing = false;
+ break;
+ }
+
+ // Let KTextEdit handle any other keys events.
+ KTextEdit::keyPressEvent ( e );
+}
+
+void KreTextEdit::setCompletedText( const QString &txt )
+{
+ int para, index;
+ getCursorPosition( &para, &index );
+
+ QString para_text = text( para );
+ int word_length = index - completion_begin;
+
+ insert( txt.right( txt.length() - word_length ) );
+ setSelection( para, index, para, completion_begin + txt.length() );
+ setCursorPosition( para, index );
+
+ completing = true;
+}
+
+void KreTextEdit::setCompletedItems( const QStringList &/*items*/ )
+{}
+
+void KreTextEdit::tryCompletion()
+{
+ int para, index;
+ getCursorPosition( &para, &index );
+
+ QString para_text = text( para );
+ if ( para_text.at( index ).isSpace() || completing ) {
+ if ( !completing )
+ completion_begin = para_text.findRev( ' ', index - 1 ) + 1;
+
+ QString completing_word = para_text.mid( completion_begin, index - completion_begin );
+
+ QString match = compObj() ->makeCompletion( completing_word );
+
+ if ( !match.isNull() && match != completing_word )
+ setCompletedText( match );
+ else
+ completing = false;
+ }
+}
+
+void KreTextEdit::rotateText( KCompletionBase::KeyBindingType type )
+{
+ KCompletion * comp = compObj();
+ if ( comp && completing &&
+ ( type == KCompletionBase::PrevCompletionMatch ||
+ type == KCompletionBase::NextCompletionMatch ) ) {
+ QString input = ( type == KCompletionBase::PrevCompletionMatch ) ? comp->previousMatch() : comp->nextMatch();
+
+ // Skip rotation if previous/next match is null or the same text
+ int para, index;
+ getCursorPosition( &para, &index );
+ QString para_text = text( para );
+ QString complete_word = para_text.mid( completion_begin, index - completion_begin );
+ if ( input.isNull() || input == complete_word )
+ return ;
+ setCompletedText( input );
+ }
+}
+
+void KreTextEdit::addCompletionItem( const QString &name )
+{
+ compObj() ->addItem( name );
+}
+
+void KreTextEdit::removeCompletionItem( const QString &name )
+{
+ compObj() ->removeItem( name );
+}
+
+void KreTextEdit::clearCompletionItems()
+{
+ compObj() ->clear();
+}
+
+#include "kretextedit.moc"
diff --git a/krecipes/src/widgets/kretextedit.h b/krecipes/src/widgets/kretextedit.h
new file mode 100644
index 0000000..3b60221
--- /dev/null
+++ b/krecipes/src/widgets/kretextedit.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef KRETEXTEDIT_H
+#define KRETEXTEDIT_H
+
+#include <ktextedit.h>
+#include <kcompletion.h>
+
+#include "datablocks/elementlist.h"
+
+/* @author Jason Kivlighn
+ * @brief An extended KTextEdit that allows for text completion
+ */
+class KreTextEdit : public KTextEdit, KCompletionBase
+{
+ Q_OBJECT
+
+public:
+ KreTextEdit( QWidget *parent );
+
+ virtual void setCompletedText( const QString &text );
+ virtual void setCompletedItems( const QStringList &items );
+
+public slots:
+ void addCompletionItem( const QString & );
+ void removeCompletionItem( const QString & );
+ void clearCompletionItems();
+
+protected:
+ void keyPressEvent( QKeyEvent * );
+
+private slots:
+ void haltCompletion();
+
+private:
+ void tryCompletion();
+ void rotateText( KCompletionBase::KeyBindingType type );
+
+ bool completing;
+ int completion_begin;
+
+};
+
+#endif //KRETEXTEDIT_H
diff --git a/krecipes/src/widgets/kwidgetlistbox.cpp b/krecipes/src/widgets/kwidgetlistbox.cpp
new file mode 100644
index 0000000..6a6be93
--- /dev/null
+++ b/krecipes/src/widgets/kwidgetlistbox.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2005 Petri Damstn <petri.damsten@iki.fi>
+ *
+ * Note: This file is now part of Krecipes, which is a slightly modified version of the
+ * original used in SuperKaramba
+ *
+ * This file is part of SuperKaramba.
+ *
+ * SuperKaramba is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * SuperKaramba is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SuperKaramba; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+#include "kwidgetlistbox.h"
+#include <kdebug.h>
+#include <kglobalsettings.h>
+
+KWidgetListbox::KWidgetListbox(QWidget *parent, const char *name)
+ : QTable(parent, name)
+{
+ setNumRows(0);
+ setNumCols(1);
+ setColumnStretchable(0, true);
+ setLeftMargin(0);
+ setTopMargin(0);
+ horizontalHeader()->hide();
+ verticalHeader()->hide();
+ setSelectionMode(QTable::NoSelection);
+ setFocusStyle(QTable::FollowStyle);
+ connect(this, SIGNAL(currentChanged(int, int)),
+ this, SLOT(selectionChanged(int, int)));
+ setHScrollBarMode(QScrollView::AlwaysOff);
+ setVScrollBarMode(QScrollView::Auto);
+}
+
+KWidgetListbox::~KWidgetListbox()
+{
+ clear();
+}
+
+void KWidgetListbox::clear()
+{
+ for(int i = 0; i < numRows(); ++i)
+ clearCellWidget(i, 0);
+ setNumRows(0);
+}
+
+int KWidgetListbox::insertItem(QWidget* item, int index)
+{
+ int row = index;
+
+ if(index == -1)
+ {
+ row = numRows();
+ }
+ //else
+ // return -1;
+
+ insertRows(row);
+ setRowHeight(row, item->height());
+ setCellWidget(row, 0, item);
+
+ for ( int i = row; i < numRows(); ++i ) {
+ setItemColors(i, even(i));
+ }
+
+ ensureCellVisible(row,0);
+
+ return row;
+}
+
+void KWidgetListbox::setSelected(QWidget* item)
+{
+ setSelected(index(item));
+}
+
+void KWidgetListbox::selectionChanged(int row, int col)
+{
+ ensureCellVisible(row, col);
+ updateColors();
+ emit selected(row);
+}
+
+void KWidgetListbox::removeItem(QWidget* item)
+{
+ removeItem(index(item));
+}
+
+void KWidgetListbox::removeItem(int index)
+{
+ removeRow(index);
+ updateColors();
+}
+
+void KWidgetListbox::setSelected(int index)
+{
+ setCurrentCell(index, 0);
+}
+
+int KWidgetListbox::selected() const
+{
+ return currentRow();
+}
+
+QWidget* KWidgetListbox::selectedItem() const
+{
+ return item(selected());
+}
+
+QWidget* KWidgetListbox::item(int index) const
+{
+ return cellWidget(index, 0);
+}
+
+int KWidgetListbox::index(QWidget* itm) const
+{
+ for(int i = 0; i < numRows(); ++i)
+ if(item(i) == itm)
+ return i;
+ return -1;
+}
+
+bool KWidgetListbox::even(int index)
+{
+ int v = 0;
+ for(int i = 0; i < numRows(); ++i)
+ {
+ if(index == i)
+ break;
+ //if(!isRowHidden(i))
+ ++v;
+ }
+ return (v%2 == 0);
+}
+
+void KWidgetListbox::updateColors()
+{
+ int v = 0;
+ for(int i = 0; i < numRows(); ++i)
+ {
+ //if(!isRowHidden(i))
+ {
+ setItemColors(i, (v%2 == 0));
+ ++v;
+ }
+ }
+}
+
+void KWidgetListbox::setItemColors(int index, bool even)
+{
+ QWidget* itm = item(index);
+if ( !itm){ kdDebug()<<"no widget at index "<<index<<endl; return; }
+/*
+ if(index == selected())
+ {
+ itm->setPaletteBackgroundColor(KGlobalSettings::highlightColor());
+ itm->setPaletteForegroundColor(KGlobalSettings::highlightedTextColor());
+ }*/
+ if(even)
+ {
+ itm->setPaletteBackgroundColor(KGlobalSettings::baseColor());
+ itm->setPaletteForegroundColor(KGlobalSettings::textColor());
+ }
+ else
+ {
+ itm->setPaletteBackgroundColor(
+ KGlobalSettings::alternateBackgroundColor());
+ itm->setPaletteForegroundColor(KGlobalSettings::textColor());
+ }
+}
+
+void KWidgetListbox::showItems(show_callback func, void* data)
+{
+ for(int i = 0; i < numRows(); ++i)
+ {
+ if(func == 0)
+ showRow(i);
+ else
+ {
+ if(func(i, item(i), data))
+ showRow(i);
+ else
+ hideRow(i);
+ }
+ }
+ updateColors();
+}
+
+void KWidgetListbox::showEvent(QShowEvent*)
+{
+ //kdDebug() << k_funcinfo << endl;
+ repaintContents(false);
+}
+
+void KWidgetListbox::paintCell(QPainter*, int, int, const QRect&,
+ bool, const QColorGroup&)
+{
+ //kdDebug() << k_funcinfo << endl;
+}
+
+#include "kwidgetlistbox.moc"
diff --git a/krecipes/src/widgets/kwidgetlistbox.h b/krecipes/src/widgets/kwidgetlistbox.h
new file mode 100644
index 0000000..d97f0bf
--- /dev/null
+++ b/krecipes/src/widgets/kwidgetlistbox.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2005 Petri Damstn <petri.damsten@iki.fi>
+ *
+ * Note: This file is now part of Krecipes, which is a slightly modified version of the
+ * original used in SuperKaramba
+ *
+ * This file is part of SuperKaramba.
+ *
+ * SuperKaramba is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * SuperKaramba is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with SuperKaramba; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+#ifndef KWIDGETLISTBOX_H
+#define KWIDGETLISTBOX_H
+
+#include <qtable.h>
+
+/**
+@author See README for the list of authors
+*/
+
+typedef bool (*show_callback) (int index, QWidget* widget, void* data);
+
+class KWidgetListbox : public QTable
+{
+ Q_OBJECT
+
+ public:
+ KWidgetListbox(QWidget *parent = 0, const char *name = 0);
+ ~KWidgetListbox();
+
+ int insertItem(QWidget* item, int index = -1);
+ void setSelected(QWidget* item);
+ void setSelected(int index);
+ void removeItem(QWidget* item);
+ void removeItem(int index);
+ void clear();
+ int selected() const;
+ QWidget* selectedItem() const;
+ QWidget* item(int index) const;
+ int index(QWidget* itm) const;
+ uint count() const { return numRows(); };
+
+ void showItems(show_callback func = 0, void* data = 0);
+
+ void paintCell(QPainter* p, int row, int col, const QRect& cr,
+ bool selected, const QColorGroup& cg);
+ protected:
+ void setItemColors(int index, bool even);
+ void updateColors();
+ bool even(int index);
+ virtual void showEvent(QShowEvent* e);
+
+ protected slots:
+ void selectionChanged(int row, int col);
+
+ signals:
+ void selected(int index);
+};
+
+#endif
diff --git a/krecipes/src/widgets/paneldeco.cpp b/krecipes/src/widgets/paneldeco.cpp
new file mode 100644
index 0000000..c1dde32
--- /dev/null
+++ b/krecipes/src/widgets/paneldeco.cpp
@@ -0,0 +1,181 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#include "paneldeco.h"
+
+#include <qpainter.h>
+#include <qpoint.h>
+#include <qrect.h>
+
+#include <kiconloader.h>
+#include <kpixmap.h>
+#include <kpixmapeffect.h>
+
+
+// Panel decoration
+
+PanelDeco::PanelDeco( QWidget *parent, const char *name, const QString &title, const QString &iconName ) : QVBox( parent, name )
+{
+
+ // Top decoration
+ tDeco = new TopDeco( this, "TopDecoration", title, iconName );
+
+ hbox = new QHBox( this );
+
+ //Left decoration
+ lDeco = new LeftDeco( hbox, "LeftDecoration" );
+
+ //The widget stack (panels)
+ stack = new QWidgetStack( hbox );
+ stack->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
+
+}
+
+
+PanelDeco::~PanelDeco()
+{}
+
+void PanelDeco::childEvent( QChildEvent *e )
+{
+ if ( e->type() == QEvent::ChildInserted ) {
+ QObject * obj = e->child();
+ if ( obj->inherits( "QWidget" ) ) {
+ QWidget * w = ( QWidget* ) obj;
+ if ( w != hbox && w != tDeco )
+ w->reparent( stack, QPoint( 0, 0 ) );
+ }
+ }
+}
+
+
+int PanelDeco::id( QWidget* w )
+{
+ return ( stack->id( w ) );
+}
+
+void PanelDeco::raise( QWidget *w )
+{
+ QWidget * old_w = visiblePanel();
+
+ stack->raiseWidget( w );
+
+ if ( old_w != w )
+ emit panelRaised( w, old_w );
+}
+
+QWidget* PanelDeco::visiblePanel( void )
+{
+ return ( stack->visibleWidget() );
+}
+
+void PanelDeco::setHeader( const QString &title, const QString &icon )
+{
+ tDeco->setHeader( title, icon );
+}
+
+// Left part of the decoration
+
+LeftDeco::LeftDeco( QWidget *parent, const char *name ) :
+#if QT_VERSION >= 0x030200
+ QWidget( parent, name, Qt::WNoAutoErase )
+#else
+ QWidget( parent, name )
+#endif
+{}
+
+LeftDeco::~LeftDeco()
+{}
+
+// Top part of the decoration
+
+TopDeco::TopDeco( QWidget *parent, const char *name, const QString &title, const QString &iconName ) :
+#if QT_VERSION >= 0x030200
+ QWidget( parent, name, Qt::WNoAutoErase )
+#else
+ QWidget( parent, name )
+#endif
+{
+ setMinimumHeight( 30 );
+ icon = 0;
+ panelTitle = QString::null;
+ if ( !iconName.isNull() ) {
+ KIconLoader il;
+ icon = new QPixmap( il.loadIcon( iconName, KIcon::NoGroup, 22 ) );
+ }
+
+ if ( !title.isNull() ) {
+ panelTitle = title;
+ }
+}
+
+TopDeco::~TopDeco()
+{
+ delete icon;
+}
+
+
+void TopDeco::paintEvent( QPaintEvent * )
+{
+ // Get gradient colors
+ QColor c1 = colorGroup().button().light( 120 );
+ QColor c2 = paletteBackgroundColor();
+
+ // Draw the gradient
+ KPixmap kpm;
+ kpm.resize( size() );
+ KPixmapEffect::unbalancedGradient ( kpm, c1, c2, KPixmapEffect::VerticalGradient, 150, 150 );
+
+ // Add a line on top
+ QPainter painter( &kpm );
+ painter.setPen( colorGroup().button().dark( 130 ) );
+ painter.drawLine( 0, 0, width(), 0 );
+
+ // Now Add the icon
+ int xpos = 0, ypos = 0;
+ if ( icon ) {
+ xpos = 20;
+ ypos = ( height() - icon->height() ) / 2 - 1;
+ painter.drawPixmap( xpos, ypos, *icon );
+ xpos += icon->width(); // Move it so that later we can easily place the text
+ }
+
+ // Finally, draw the text besides the icon
+ if ( !panelTitle.isNull() ) {
+ xpos += 15;
+ QRect r = rect();
+ r.setLeft( xpos );
+ painter.setPen( QColor( 0x00, 0x00, 0x00 ) );
+ QFont ft = font();
+ ft.setBold( true );
+ painter.setFont( ft );
+ painter.drawText( r, Qt::AlignVCenter, panelTitle );
+ }
+ painter.end();
+ // Copy the pixmap to the widget
+ bitBlt( this, 0, 0, &kpm );
+}
+
+void TopDeco::setHeader( const QString &title, const QString &iconName )
+{
+ if ( !title.isNull() )
+ panelTitle = title;
+ if ( !iconName.isNull() ) {
+ KIconLoader il;
+ icon = new QPixmap( il.loadIcon( iconName, KIcon::NoGroup, 22 ) );
+ }
+ if ( !title.isNull() || !iconName.isNull() )
+ update();
+}
+
+QSize TopDeco::sizeHint( void )
+{
+ return ( QSize( parentWidget() ->width(), 30 ) );
+}
+
+#include "paneldeco.moc"
diff --git a/krecipes/src/widgets/paneldeco.h b/krecipes/src/widgets/paneldeco.h
new file mode 100644
index 0000000..10ac326
--- /dev/null
+++ b/krecipes/src/widgets/paneldeco.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+* Copyright (C) 2003 by Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+#ifndef PANELDECO_H
+#define PANELDECO_H
+
+
+#include <qevent.h>
+#include <qiconset.h>
+#include <qstring.h>
+#include <qhbox.h>
+#include <qpixmap.h>
+#include <qvbox.h>
+#include <qwidget.h>
+#include <qwidgetstack.h>
+
+
+/**
+* @author Unai Garro
+*/
+
+class PanelDeco;
+class LeftDeco;
+class TopDeco;
+
+class PanelDeco : public QVBox
+{
+ Q_OBJECT
+public:
+ // Methods
+ PanelDeco( QWidget *parent = 0, const char *name = 0, const QString &title = QString::null, const QString &iconName = QString::null );
+ ~PanelDeco();
+ int id( QWidget* w ); // obtain the id of the given panel
+ QWidget* visiblePanel( void ); // obtain the current active panel no.
+
+signals:
+ void panelRaised( QWidget *w, QWidget *old_w );
+
+private:
+ QHBox *hbox;
+ LeftDeco *lDeco;
+ TopDeco *tDeco;
+ QWidgetStack *stack;
+
+public slots:
+ void raise( QWidget *w );
+ void setHeader( const QString &title = QString::null, const QString &icon = QString::null );
+protected:
+ virtual void childEvent( QChildEvent *e );
+
+
+};
+
+class LeftDeco: public QWidget
+{
+ Q_OBJECT
+public:
+ LeftDeco( QWidget *parent = 0, const char *name = 0 );
+
+ ~LeftDeco();
+};
+
+class TopDeco: public QWidget
+{
+ Q_OBJECT
+public:
+ TopDeco( QWidget *parent = 0, const char *name = 0, const QString &title = QString::null, const QString &iconName = QString::null );
+ ~TopDeco();
+ virtual QSize sizeHint( void );
+public slots:
+ void setHeader( const QString &title = QString::null, const QString &iconName = QString::null );
+protected:
+ virtual void paintEvent( QPaintEvent *e );
+private:
+ QPixmap *icon;
+ QString panelTitle;
+};
+
+#endif
diff --git a/krecipes/src/widgets/prepmethodcombobox.cpp b/krecipes/src/widgets/prepmethodcombobox.cpp
new file mode 100644
index 0000000..ec5a4bb
--- /dev/null
+++ b/krecipes/src/widgets/prepmethodcombobox.cpp
@@ -0,0 +1,186 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "prepmethodcombobox.h"
+
+#include <qlistbox.h>
+
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/elementlist.h"
+
+/** Completion object which allows completing completing items
+ * the last item in a comma-separated list
+ */
+class PrepMethodCompletion : public KCompletion
+{
+public:
+ PrepMethodCompletion() : KCompletion()
+ {}
+
+ virtual QString makeCompletion( const QString &string ) {
+ kdDebug()<<"original makeCompletion( "<<string<<" )"<<endl;
+
+ int comma_index = string.findRev(",");
+ QString completion_txt = string;
+ if ( comma_index != -1 )
+ completion_txt = completion_txt.right( completion_txt.length() - comma_index - 1 ).stripWhiteSpace();
+ if ( completion_txt.isEmpty() )
+ return string;
+
+ kdDebug()<<"altered makeCompletion( "<<completion_txt<<" )"<<endl;
+
+ completion_txt = KCompletion::makeCompletion(completion_txt);
+ kdDebug()<<"got: "<<completion_txt<<endl;
+
+ if ( completion_txt.isEmpty() )
+ completion_txt = string;
+ else if ( comma_index != -1 )
+ completion_txt = string.left( comma_index ) + "," + completion_txt;
+
+ kdDebug()<<"returning: "<<completion_txt<<endl;
+ return completion_txt;
+ }
+};
+
+PrepMethodComboBox::PrepMethodComboBox( bool b, QWidget *parent, RecipeDB *db, const QString &specialItem ) :
+ KComboBox( b, parent ),
+ database( db ), m_specialItem(specialItem)
+{
+ setAutoDeleteCompletionObject(true);
+ setCompletionObject(new PrepMethodCompletion());
+}
+
+void PrepMethodComboBox::reload()
+{
+ QString remember_text;
+ if ( editable() )
+ remember_text = lineEdit()->text();
+
+ ElementList prepMethodList;
+ database->loadPrepMethods( &prepMethodList );
+
+ clear();
+ prepMethodComboRows.clear();
+
+ int row = 0;
+ if ( !m_specialItem.isNull() ) {
+ insertItem(m_specialItem);
+ prepMethodComboRows.insert( row, -1 );
+ row++;
+ }
+ for ( ElementList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it, ++row ) {
+ insertItem((*it).name);
+ completionObject()->addItem((*it).name);
+ prepMethodComboRows.insert( row,(*it).id );
+ }
+
+ if ( editable() )
+ lineEdit()->setText( remember_text );
+
+ database->disconnect( this );
+ connect( database, SIGNAL( prepMethodCreated( const Element & ) ), SLOT( createPrepMethod( const Element & ) ) );
+ connect( database, SIGNAL( prepMethodRemoved( int ) ), SLOT( removePrepMethod( int ) ) );
+}
+
+int PrepMethodComboBox::id( int row )
+{
+ return prepMethodComboRows[ row ];
+}
+
+int PrepMethodComboBox::id( const QString &ing )
+{
+ for ( int i = 0; i < count(); i++ ) {
+ if ( ing == text( i ) )
+ return id(i);
+ }
+ kdDebug()<<"Warning: couldn't find the ID for "<<ing<<endl;
+ return -1;
+}
+
+void PrepMethodComboBox::createPrepMethod( const Element &element )
+{
+ int row = findInsertionPoint( element.name );
+
+ QString remember_text;
+ if ( editable() )
+ remember_text = lineEdit()->text();
+
+ insertItem( element.name, row );
+ completionObject()->addItem(element.name);
+
+ if ( editable() )
+ lineEdit()->setText( remember_text );
+
+ //now update the map by pushing everything after this item down
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) {
+ if ( it.key() >= row ) {
+ new_map.insert( it.key() + 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ prepMethodComboRows = new_map;
+ prepMethodComboRows.insert( row, element.id );
+}
+
+void PrepMethodComboBox::removePrepMethod( int id )
+{
+ int row = -1;
+ for ( QMap<int, int>::iterator it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) {
+ if ( it.data() == id ) {
+ row = it.key();
+ completionObject()->removeItem( text(row) );
+ removeItem( row );
+ prepMethodComboRows.remove( it );
+ break;
+ }
+ }
+
+ if ( row == -1 )
+ return ;
+
+ //now update the map by pushing everything after this item up
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) {
+ if ( it.key() > row ) {
+ new_map.insert( it.key() - 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ prepMethodComboRows = new_map;
+}
+
+int PrepMethodComboBox::findInsertionPoint( const QString &name )
+{
+ for ( int i = 0; i < count(); i++ ) {
+ if ( QString::localeAwareCompare( name, text( i ) ) < 0 )
+ return i;
+ }
+
+ return count();
+}
+
+void PrepMethodComboBox::setSelected( int prepID )
+{
+ //do a reverse lookup on the row->id map
+ QMap<int, int>::const_iterator it;
+ for ( it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) {
+ if ( it.data() == prepID ) {
+ setCurrentItem(it.key());
+ break;
+ }
+ }
+}
+
+#include "prepmethodcombobox.moc"
diff --git a/krecipes/src/widgets/prepmethodcombobox.h b/krecipes/src/widgets/prepmethodcombobox.h
new file mode 100644
index 0000000..e4b4d48
--- /dev/null
+++ b/krecipes/src/widgets/prepmethodcombobox.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+* Copyright (C) 2005 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PREPMETHODCOMBOBOX_H
+#define PREPMETHODCOMBOBOX_H
+
+#include <kcombobox.h>
+
+#include <qmap.h>
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+class ElementList;
+
+class PrepMethodComboBox : public KComboBox
+{
+ Q_OBJECT
+
+public:
+ PrepMethodComboBox( bool, QWidget *parent, RecipeDB *db, const QString &specialItem = QString::null );
+
+ void reload();
+ int id( int row );
+ int id( const QString &ing );
+ void setSelected( int prepID );
+
+private slots:
+ void createPrepMethod( const Element &element );
+ void removePrepMethod( int id );
+
+ int findInsertionPoint( const QString &name );
+
+private:
+ RecipeDB *database;
+ QMap<int, int> prepMethodComboRows; // Contains the prep method id for every given row in the combobox
+ QString m_specialItem;
+};
+
+#endif //PREPMETHODCOMBOBOX_H
+
diff --git a/krecipes/src/widgets/prepmethodlistview.cpp b/krecipes/src/widgets/prepmethodlistview.cpp
new file mode 100644
index 0000000..a4b148e
--- /dev/null
+++ b/krecipes/src/widgets/prepmethodlistview.cpp
@@ -0,0 +1,189 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "prepmethodlistview.h"
+
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include "backends/recipedb.h"
+#include "dialogs/createelementdialog.h"
+#include "dialogs/dependanciesdialog.h"
+
+PrepMethodListView::PrepMethodListView( QWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db,db->prepMethodCount())
+{
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+}
+
+void PrepMethodListView::init()
+{
+ connect( database, SIGNAL( prepMethodCreated( const Element & ) ), SLOT( checkCreatePrepMethod( const Element & ) ) );
+ connect( database, SIGNAL( prepMethodRemoved( int ) ), SLOT( removePrepMethod( int ) ) );
+}
+
+void PrepMethodListView::load( int limit, int offset )
+{
+ ElementList prepMethodList;
+ database->loadPrepMethods( &prepMethodList, limit, offset );
+
+ setTotalItems(prepMethodList.count());
+
+ for ( ElementList::const_iterator ing_it = prepMethodList.begin(); ing_it != prepMethodList.end(); ++ing_it )
+ createPrepMethod( *ing_it );
+}
+
+void PrepMethodListView::checkCreatePrepMethod( const Element &el )
+{
+ if ( handleElement(el.name) ) { //only create this prep method if the base class okays it
+ createPrepMethod(el);
+ }
+}
+
+
+StdPrepMethodListView::StdPrepMethodListView( QWidget *parent, RecipeDB *db, bool editable ) : PrepMethodListView( parent, db )
+{
+ addColumn( i18n( "Preparation Method" ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+
+ KIconLoader *il = new KIconLoader;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il->loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_C );
+ kpop->insertItem( il->loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->polish();
+
+ delete il;
+
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem* ) ), this, SLOT( modPrepMethod( QListViewItem* ) ) );
+ connect( this, SIGNAL( itemRenamed( QListViewItem* ) ), this, SLOT( savePrepMethod( QListViewItem* ) ) );
+ }
+}
+
+void StdPrepMethodListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdPrepMethodListView::createNew()
+{
+ CreateElementDialog * elementDialog = new CreateElementDialog( this, i18n( "New Preparation Method" ) );
+
+ if ( elementDialog->exec() == QDialog::Accepted ) {
+ QString result = elementDialog->newElementName();
+
+ //check bounds first
+ if ( checkBounds( result ) )
+ database->createNewPrepMethod( result ); // Create the new prepMethod in the database
+ }
+}
+
+void StdPrepMethodListView::remove
+ ()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item ) {
+ ElementList dependingRecipes;
+ int prepMethodID = item->text( 1 ).toInt();
+ database->findPrepMethodDependancies( prepMethodID, &dependingRecipes );
+ if ( dependingRecipes.isEmpty() )
+ database->removePrepMethod( prepMethodID );
+ else // Need Warning!
+ {
+ ListInfo info;
+ info.list = dependingRecipes;
+ info.name = i18n("Recipes");
+ DependanciesDialog warnDialog( this, info );
+ warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") );
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removePrepMethod( prepMethodID );
+ }
+ }
+}
+
+void StdPrepMethodListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item )
+ PrepMethodListView::rename( item, 0 );
+}
+
+void StdPrepMethodListView::createPrepMethod( const Element &ing )
+{
+ createElement(new QListViewItem( this, ing.name, QString::number( ing.id ) ));
+}
+
+void StdPrepMethodListView::removePrepMethod( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 1 );
+ removeElement(item);
+}
+
+void StdPrepMethodListView::modPrepMethod( QListViewItem* i )
+{
+ if ( i )
+ PrepMethodListView::rename( i, 0 );
+}
+
+void StdPrepMethodListView::savePrepMethod( QListViewItem* i )
+{
+ if ( !checkBounds( i->text( 0 ) ) ) {
+ reload(ForceReload); //reset the changed text
+ return ;
+ }
+
+ int existing_id = database->findExistingPrepByName( i->text( 0 ) );
+ int prep_id = i->text( 1 ).toInt();
+ if ( existing_id != -1 && existing_id != prep_id ) //category already exists with this label... merge the two
+ {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This preparation method already exists. Continuing will merge these two into one. Are you sure?" ) ) )
+ {
+ case KMessageBox::Continue: {
+ database->mergePrepMethods( existing_id, prep_id );
+ break;
+ }
+ default:
+ reload(ForceReload);
+ break;
+ }
+ }
+ else {
+ database->modPrepMethod( ( i->text( 1 ) ).toInt(), i->text( 0 ) );
+ }
+}
+
+bool StdPrepMethodListView::checkBounds( const QString &name )
+{
+ if ( name.length() > uint(database->maxPrepMethodNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) );
+ return false;
+ }
+
+ return true;
+}
+
+#include "prepmethodlistview.moc"
diff --git a/krecipes/src/widgets/prepmethodlistview.h b/krecipes/src/widgets/prepmethodlistview.h
new file mode 100644
index 0000000..f9fd266
--- /dev/null
+++ b/krecipes/src/widgets/prepmethodlistview.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PREPMETHODLISTVIEW_H
+#define PREPMETHODLISTVIEW_H
+
+#include "dblistviewbase.h"
+
+#include "datablocks/element.h"
+
+class RecipeDB;
+class KPopupMenu;
+
+class PrepMethodListView : public DBListViewBase
+{
+ Q_OBJECT
+
+public:
+ PrepMethodListView( QWidget *parent, RecipeDB *db );
+
+public slots:
+ virtual void load( int curr_limit, int curr_offset );
+
+protected slots:
+ virtual void createPrepMethod( const Element & ) = 0;
+ virtual void removePrepMethod( int ) = 0;
+
+ void checkCreatePrepMethod( const Element &el );
+
+protected:
+ virtual void init();
+};
+
+
+class StdPrepMethodListView : public PrepMethodListView
+{
+ Q_OBJECT
+
+public:
+ StdPrepMethodListView( QWidget *parent, RecipeDB *db, bool editable = false );
+
+protected:
+ virtual void createPrepMethod( const Element & );
+ virtual void removePrepMethod( int );
+
+private slots:
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void createNew();
+ void remove
+ ();
+ void rename();
+
+ void modPrepMethod( QListViewItem* i );
+ void savePrepMethod( QListViewItem* i );
+
+private:
+ bool checkBounds( const QString &name );
+
+ KPopupMenu *kpop;
+};
+
+#endif //PREPMETHODLISTVIEW_H
diff --git a/krecipes/src/widgets/propertylistview.cpp b/krecipes/src/widgets/propertylistview.cpp
new file mode 100644
index 0000000..2e94788
--- /dev/null
+++ b/krecipes/src/widgets/propertylistview.cpp
@@ -0,0 +1,289 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "propertylistview.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "dialogs/createpropertydialog.h"
+
+PropertyCheckListItem::PropertyCheckListItem( QListView* klv, const IngredientProperty &property ) : QCheckListItem( klv, QString::null, QCheckListItem::CheckBox ),
+ m_property( property )
+{
+ //setOn( false ); // Set unchecked by default
+}
+
+PropertyCheckListItem::PropertyCheckListItem( QListViewItem* it, const IngredientProperty &property ) : QCheckListItem( it, QString::null, QCheckListItem::CheckBox ),
+ m_property( property )
+{
+ //setOn( false ); // Set unchecked by default
+}
+
+QString PropertyCheckListItem::text( int column ) const
+{
+ switch ( column ) {
+ case 0:
+ return m_property.name;
+ break;
+ case 1:
+ return m_property.units;
+ break;
+ case 2:
+ return QString::number( m_property.id );
+ break;
+
+ }
+
+ return QString::null;
+}
+
+
+HidePropertyCheckListItem::HidePropertyCheckListItem( QListView* klv, const IngredientProperty &property, bool enable ) : PropertyCheckListItem( klv, property )
+{
+ m_holdSettings = true;
+ setOn( enable ); // Set checked by default
+ m_holdSettings = false;
+}
+
+HidePropertyCheckListItem::HidePropertyCheckListItem( QListViewItem* it, const IngredientProperty &property, bool enable ) : PropertyCheckListItem( it, property )
+{
+ m_holdSettings = true;
+ setOn( enable ); // Set checked by default
+ m_holdSettings = false;
+}
+
+void HidePropertyCheckListItem::stateChange( bool on )
+{
+ if ( !m_holdSettings ) {
+ KConfig *config = KGlobal::config();
+ config->setGroup("Formatting");
+
+ config->sync();
+ QStringList hiddenList = config->readListEntry("HiddenProperties");
+ if ( on )
+ hiddenList.remove(m_property.name);
+ else if ( !hiddenList.contains(m_property.name) )
+ hiddenList.append(m_property.name);
+
+ config->writeEntry("HiddenProperties",hiddenList);
+ }
+}
+
+PropertyListView::PropertyListView( QWidget *parent, RecipeDB *db ) : KListView( parent ),
+ database( db )
+{
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+
+ connect( db, SIGNAL( propertyCreated( const IngredientProperty & ) ), SLOT( createProperty( const IngredientProperty & ) ) );
+ connect( db, SIGNAL( propertyRemoved( int ) ), SLOT( removeProperty( int ) ) );
+}
+
+void PropertyListView::reload()
+{
+ clear(); // Clear the view
+
+ m_loading = true;
+
+ IngredientPropertyList propertyList;
+ database->loadProperties( &propertyList );
+
+ //Populate this data into the KListView
+ IngredientPropertyList::const_iterator prop_it;
+ for ( prop_it = propertyList.begin(); prop_it != propertyList.end(); ++prop_it )
+ createProperty( *prop_it );
+
+ m_loading = false;
+}
+
+
+
+StdPropertyListView::StdPropertyListView( QWidget *parent, RecipeDB *db, bool editable ) : PropertyListView( parent, db )
+{
+ addColumn( i18n( "Property" ) );
+ addColumn( i18n( "Units" ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 2 );
+
+ setSorting( 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+
+ KIconLoader *il = new KIconLoader;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il->loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_C );
+ kpop->insertItem( il->loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il->loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->polish();
+
+ delete il;
+
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem* ) ), this, SLOT( modProperty( QListViewItem* ) ) );
+ connect( this, SIGNAL( itemRenamed( QListViewItem* ) ), this, SLOT( saveProperty( QListViewItem* ) ) );
+ }
+}
+
+void StdPropertyListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdPropertyListView::createNew()
+{
+ UnitList list;
+ database->loadUnits( &list );
+ CreatePropertyDialog* propertyDialog = new CreatePropertyDialog( this, &list );
+
+ if ( propertyDialog->exec() == QDialog::Accepted ) {
+ QString name = propertyDialog->newPropertyName();
+ QString units = propertyDialog->newUnitsName();
+ if ( !( ( name.isEmpty() ) || ( units.isEmpty() ) ) ) // Make sure none of the fields are empty
+ {
+ //check bounds first
+ if ( checkBounds( name ) )
+ database->addProperty( name, units );
+ }
+ }
+ delete propertyDialog;
+}
+
+void StdPropertyListView::remove
+ ()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item ) {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to delete this property?" ) ) ) {
+ case KMessageBox::Continue:
+ database->removeProperty( item->text( 2 ).toInt() );
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void StdPropertyListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item )
+ PropertyListView::rename( item, 0 );
+}
+
+void StdPropertyListView::removeProperty( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 2 );
+
+ Q_ASSERT( item );
+
+ delete item;
+}
+
+void StdPropertyListView::createProperty( const IngredientProperty &property )
+{
+ ( void ) new QListViewItem( this, property.name, property.units, QString::number( property.id ) );
+}
+
+void StdPropertyListView::modProperty( QListViewItem* i )
+{
+ if ( i )
+ PropertyListView::rename( i, 0 );
+}
+
+void StdPropertyListView::saveProperty( QListViewItem* i )
+{
+ if ( !checkBounds( i->text( 0 ) ) ) {
+ reload(); //reset the changed text
+ return ;
+ }
+kdDebug() << "saveProp: " << i->text( 0 ) << endl;
+ int existing_id = database->findExistingPropertyByName( i->text( 0 ) );
+ int prop_id = i->text( 2 ).toInt();
+ if ( existing_id != -1 && existing_id != prop_id ) //category already exists with this label... merge the two
+ {
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This property already exists. Continuing will merge these two properties into one. Are you sure?" ) ) )
+ {
+ case KMessageBox::Continue: {
+ database->mergeProperties( existing_id, prop_id );
+ break;
+ }
+ default:
+ reload();
+ break;
+ }
+ }
+ else
+ database->modProperty( prop_id, i->text( 0 ) );
+}
+
+bool StdPropertyListView::checkBounds( const QString &name )
+{
+ if ( name.length() > database->maxPropertyNameLength() ) {
+ KMessageBox::error( this, QString( i18n( "Property name cannot be longer than %1 characters." ) ).arg( database->maxPropertyNameLength() ) );
+ return false;
+ }
+
+ return true;
+}
+
+
+
+PropertyConstraintListView::PropertyConstraintListView( QWidget *parent, RecipeDB *db ) : PropertyListView( parent, db )
+{
+ addColumn( i18n( "Enabled" ) );
+ addColumn( i18n( "Property" ) );
+ addColumn( i18n( "Min. Value" ) );
+ addColumn( i18n( "Max. Value" ) );
+ addColumn( "Id", 0 ); //hidden, only for internal purposes
+
+ setRenameable( 0, true );
+}
+
+void PropertyConstraintListView::removeProperty( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 4 );
+
+ Q_ASSERT( item );
+
+ delete item;
+}
+
+void PropertyConstraintListView::createProperty( const IngredientProperty &property )
+{
+ ( void ) new ConstraintsListItem( this, property );
+}
+
+
+CheckPropertyListView::CheckPropertyListView( QWidget *parent, RecipeDB *db, bool editable ) : StdPropertyListView( parent, db, editable )
+{
+}
+
+void CheckPropertyListView::createProperty( const IngredientProperty &property )
+{
+ ( void ) new HidePropertyCheckListItem( this, property, (m_loading)?false:true );
+}
+
+#include "propertylistview.moc"
diff --git a/krecipes/src/widgets/propertylistview.h b/krecipes/src/widgets/propertylistview.h
new file mode 100644
index 0000000..f578c1c
--- /dev/null
+++ b/krecipes/src/widgets/propertylistview.h
@@ -0,0 +1,203 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef PROPERTYLISTVIEW_H
+#define PROPERTYLISTVIEW_H
+
+#include <klistview.h>
+
+#include "datablocks/element.h"
+#include "datablocks/ingredientproperty.h"
+#include "datablocks/constraintlist.h"
+
+class RecipeDB;
+class KPopupMenu;
+
+class PropertyCheckListItem : public QCheckListItem
+{
+public:
+ PropertyCheckListItem( QListView* klv, const IngredientProperty &property );
+ PropertyCheckListItem( QListViewItem* it, const IngredientProperty &property );
+
+ ~PropertyCheckListItem( void )
+ {}
+ virtual QString text( int column ) const;
+
+ IngredientProperty property() const
+ {
+ return m_property;
+ }
+
+protected:
+ IngredientProperty m_property;
+
+};
+
+class HidePropertyCheckListItem : public PropertyCheckListItem
+{
+public:
+ HidePropertyCheckListItem( QListView* klv, const IngredientProperty &property, bool enable = false );
+ HidePropertyCheckListItem( QListViewItem* it, const IngredientProperty &property, bool enable = false );
+
+protected:
+ virtual void stateChange( bool on );
+
+private:
+ bool m_holdSettings;
+};
+
+
+class ConstraintsListItem: public QCheckListItem
+{
+public:
+ ConstraintsListItem( QListView* klv, const IngredientProperty &pty ) : QCheckListItem( klv, QString::null, QCheckListItem::CheckBox )
+ {
+ // Initialize the constraint data with the the property data
+ ctStored = new Constraint();
+ ctStored->id = pty.id;
+ ctStored->name = pty.name;
+ ctStored->perUnit = pty.perUnit;
+ ctStored->units = pty.units;
+ ctStored->max = 0;
+ ctStored->min = 0;
+ }
+
+ ~ConstraintsListItem( void )
+ {
+ delete ctStored;
+ }
+
+private:
+ Constraint *ctStored;
+
+public:
+ void setConstraint( const Constraint &constraint )
+ {
+ delete ctStored;
+ ctStored = new Constraint( constraint );
+
+ setOn( ctStored->enabled );
+ }
+ double maxVal()
+ {
+ return ctStored->max;
+ }
+ double minVal()
+ {
+ return ctStored->min;
+ }
+ int propertyId()
+ {
+ return ctStored->id;
+ }
+ void setMax( double maxValue )
+ {
+ ctStored->max = maxValue;
+ setText( 3, QString::number( maxValue ) );
+ }
+ void setMin( double minValue )
+ {
+ ctStored->min = minValue;
+ setText( 2, QString::number( minValue ) );
+ }
+ virtual QString text( int column ) const
+ {
+ switch ( column ) {
+ case 1:
+ return ( ctStored->name );
+ case 2:
+ return ( QString::number( ctStored->min ) );
+ case 3:
+ return ( QString::number( ctStored->max ) );
+ case 4:
+ return ( QString::number( ctStored->id ) );
+ default:
+ return ( QString::null );
+ }
+ }
+};
+
+
+class PropertyListView : public KListView
+{
+ Q_OBJECT
+
+public:
+ PropertyListView( QWidget *parent, RecipeDB * );
+
+public slots:
+ void reload( void );
+
+protected:
+ RecipeDB *database;
+ bool m_loading;
+
+protected slots:
+ virtual void removeProperty( int id ) = 0;
+ virtual void createProperty( const IngredientProperty &property ) = 0;
+};
+
+
+
+class StdPropertyListView : public PropertyListView
+{
+ Q_OBJECT
+
+public:
+ StdPropertyListView( QWidget *parent, RecipeDB *, bool editable = false );
+
+protected:
+ virtual void removeProperty( int id );
+ virtual void createProperty( const IngredientProperty &property );
+
+private slots:
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+
+ void createNew();
+ void remove
+ ();
+ void rename();
+
+ void modProperty( QListViewItem* i );
+ void saveProperty( QListViewItem* i );
+
+private:
+ bool checkBounds( const QString &name );
+
+ KPopupMenu *kpop;
+};
+
+
+
+class PropertyConstraintListView : public PropertyListView
+{
+public:
+ PropertyConstraintListView( QWidget *parent, RecipeDB * );
+
+protected:
+ virtual void removeProperty( int id );
+ virtual void createProperty( const IngredientProperty &property );
+};
+
+class CheckPropertyListView : public StdPropertyListView
+{
+ Q_OBJECT
+
+public:
+ CheckPropertyListView( QWidget *parent, RecipeDB *, bool editable = false );
+
+protected:
+ virtual void createProperty( const IngredientProperty &property );
+
+private:
+ bool checkBounds( const QString &name );
+};
+
+#endif //PROPERTYLISTVIEW_H
diff --git a/krecipes/src/widgets/ratingdisplaywidget.ui b/krecipes/src/widgets/ratingdisplaywidget.ui
new file mode 100644
index 0000000..c4387a4
--- /dev/null
+++ b/krecipes/src/widgets/ratingdisplaywidget.ui
@@ -0,0 +1,239 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>RatingDisplayWidget</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>RatingDisplayWidget</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>533</width>
+ <height>211</height>
+ </rect>
+ </property>
+ <property name="paletteBackgroundColor">
+ <color>
+ <red>250</red>
+ <green>248</green>
+ <blue>241</blue>
+ </color>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer2_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>icon</cstring>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>76</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ <property name="alignment">
+ <set>AlignCenter</set>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>21</width>
+ <height>80</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>raterName</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <bold>1</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Rater</string>
+ </property>
+ </widget>
+ <widget class="KListView">
+ <column>
+ <property name="text">
+ <string>Criteria</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Stars</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>criteriaListView</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="selectionMode" stdset="0">
+ <enum>NoSelection</enum>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>comment</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Comments</string>
+ </property>
+ <property name="alignment">
+ <set>WordBreak|AlignTop</set>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>150</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>buttonRemove</cstring>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>buttonEdit</cstring>
+ </property>
+ <property name="text">
+ <string>Edit...</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ </hbox>
+</widget>
+<includes>
+ <include location="local" impldecl="in declaration">datablocks/rating.h</include>
+</includes>
+<variables>
+ <variable access="public">RatingList::iterator rating_it;</variable>
+</variables>
+<layoutdefaults spacing="6" margin="6"/>
+<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/>
+<includehints>
+ <includehint>klistview.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+</includehints>
+</UI>
diff --git a/krecipes/src/widgets/ratingwidget.cpp b/krecipes/src/widgets/ratingwidget.cpp
new file mode 100644
index 0000000..ed5580f
--- /dev/null
+++ b/krecipes/src/widgets/ratingwidget.cpp
@@ -0,0 +1,164 @@
+/***************************************************************************
+ copyright : (C) 2003-2005 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "ratingwidget.h"
+
+#include <kglobal.h> // needed for KMAX
+#include <kiconloader.h>
+#include <kdebug.h>
+
+#include <qintdict.h>
+#include <qlayout.h>
+
+namespace {
+ static const int RATING_WIDGET_MAX_STAR_SIZE = 24;
+}
+
+const QPixmap& RatingWidget::pixmap(const QString& value_) {
+ static QIntDict<QPixmap> pixmaps;
+ if(pixmaps.isEmpty()) {
+ pixmaps.insert(-1, new QPixmap());
+ }
+ bool ok;
+ int n = value_.toInt(&ok);
+ if(!ok || n < 1 || n > 10) {
+ return *pixmaps[-1];
+ }
+ if(pixmaps[n]) {
+ return *pixmaps[n];
+ }
+
+ QString picName = QString::fromLatin1("stars%1").arg(n);
+ QPixmap* pix = new QPixmap(UserIcon(picName));
+ pixmaps.insert(n, pix);
+ return *pix;
+}
+
+RatingWidget::RatingWidget(int stars, QWidget* parent_, const char* name_/*=0*/)
+ : QHBox(parent_, name_), m_currIndex(-1), m_min(0), m_max(stars*2) {
+ m_pixOn = UserIcon(QString::fromLatin1("star_on"));
+ m_pixOff = UserIcon(QString::fromLatin1("star_off"));
+ m_pixHalf = UserIcon(QString::fromLatin1("star_half"));
+ setSpacing(0);
+
+ // find maximum width and height
+ int w = KMAX(RATING_WIDGET_MAX_STAR_SIZE, KMAX(m_pixOn.width(), m_pixOff.width()));
+ int h = KMAX(RATING_WIDGET_MAX_STAR_SIZE, KMAX(m_pixOn.height(), m_pixOff.height()));
+ for(int i = 0; i < stars; ++i) {
+ QLabel* l = new QLabel(this);
+ l->setFixedSize(w, h);
+ m_widgets.append(l);
+ }
+ init();
+
+ QBoxLayout* l = dynamic_cast<QBoxLayout*>(layout());
+ if(l) {
+ l->addStretch(1);
+ }
+}
+
+void RatingWidget::init() {
+ m_total = KMIN(m_max/2, static_cast<int>(m_widgets.count()));
+ uint i = 0;
+ for( ; static_cast<int>(i) < m_total; ++i) {
+ m_widgets.at(i)->setPixmap(m_pixOff);
+ }
+ for( ; i < m_widgets.count(); ++i) {
+ m_widgets.at(i)->setPixmap(QPixmap());
+ }
+ update();
+}
+
+void RatingWidget::update() {
+ int i = 0;
+ for( ; i <= (m_currIndex-1)/2; ++i) {
+ m_widgets.at(i)->setPixmap(m_pixOn);
+ }
+ for( ; i < m_total; ++i) {
+ m_widgets.at(i)->setPixmap(m_pixOff);
+ }
+
+ if ( m_currIndex % 2 == 0 ) {
+ m_widgets.at(m_currIndex/2)->setPixmap(m_pixHalf);
+ }
+
+ QHBox::update();
+}
+
+void RatingWidget::mousePressEvent(QMouseEvent* event_) {
+ // only react to left button
+ if(event_->button() != Qt::LeftButton) {
+ return;
+ }
+
+ int idx;
+ QWidget* child = childAt(event_->pos());
+ bool left = false;
+ if(child) {
+ QRect child_geom_left_half = child->geometry();
+ child_geom_left_half.setWidth(child_geom_left_half.width()/2);
+ if ( child_geom_left_half.contains(event_->pos()) )
+ left = true;
+
+ idx = m_widgets.findRef(static_cast<QLabel*>(child));
+ // if the widget is clicked beyond the maximum value, clear it
+ // remember total and min are values, but index is zero-based!
+ if(idx > m_total-1) {
+ idx = -1;
+ } else if(idx < m_min-1) {
+ idx = m_min-1; // limit to minimum, remember index is zero-based
+ }
+ } else {
+ idx = -1;
+ }
+
+ int oldCurrent = m_currIndex;
+
+ m_currIndex = idx*2+1;
+
+ if ( left )
+ m_currIndex--;
+
+ if ( oldCurrent != m_currIndex ) {
+ update();
+ emit modified();
+ }
+}
+
+void RatingWidget::clear() {
+ m_currIndex = -1;
+ update();
+}
+
+QString RatingWidget::text() const {
+ // index is index of the list, which is zero-based. Add 1!
+ return m_currIndex == -1 ? QString::null : QString::number(double(m_currIndex+1)/2);
+}
+
+void RatingWidget::setText(const QString& text_) {
+ bool ok;
+ // text is value, subtract one to get index
+ m_currIndex =text_.toInt(&ok)-1;
+ if(ok) {
+ if(m_currIndex > m_total-1) {
+ m_currIndex = -1;
+ } else if(m_currIndex < m_min-1) {
+ m_currIndex = m_min-1; // limit to minimum, remember index is zero-based
+ }
+ } else {
+ m_currIndex = -1;
+ }
+ update();
+}
+
+#include "ratingwidget.moc"
diff --git a/krecipes/src/widgets/ratingwidget.h b/krecipes/src/widgets/ratingwidget.h
new file mode 100644
index 0000000..ac7e213
--- /dev/null
+++ b/krecipes/src/widgets/ratingwidget.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ copyright : (C) 2003-2005 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#ifndef RATINGWIDGET_H
+#define RATINGWIDGET_H
+
+#include <qhbox.h>
+#include <qptrlist.h>
+#include <qlabel.h>
+#include <qpixmap.h>
+#include <qstringlist.h>
+
+/**
+ * @author Robby Stephenson
+ */
+class RatingWidget : public QHBox {
+Q_OBJECT
+
+typedef QPtrList<QLabel> LabelList;
+
+public:
+ RatingWidget(int stars, QWidget* parent, const char* name = 0);
+
+ void clear();
+ QString text() const;
+ void setText(const QString& text);
+
+ static const QPixmap& pixmap(const QString& value);
+
+public slots:
+ void update();
+
+signals:
+ void modified();
+
+protected:
+ virtual void mousePressEvent(QMouseEvent* e);
+
+private:
+ void init();
+
+ LabelList m_widgets;
+
+ int m_currIndex;
+ int m_total;
+ int m_min;
+ int m_max;
+
+ QPixmap m_pixOn;
+ QPixmap m_pixOff;
+ QPixmap m_pixHalf;
+};
+#endif
diff --git a/krecipes/src/widgets/recipelistview.cpp b/krecipes/src/widgets/recipelistview.cpp
new file mode 100644
index 0000000..9740aa1
--- /dev/null
+++ b/krecipes/src/widgets/recipelistview.cpp
@@ -0,0 +1,447 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "recipelistview.h"
+
+#include <qintdict.h>
+#include <qdatastream.h>
+#include <qtooltip.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kprogress.h>
+
+#include "backends/recipedb.h"
+
+class UncategorizedItem : public QListViewItem
+{
+public:
+ UncategorizedItem( QListView *lv ) : QListViewItem( lv, i18n("Uncategorized") ){}
+ int rtti() const { return 1006; }
+};
+
+RecipeItemDrag::RecipeItemDrag( RecipeListItem *recipeItem, QWidget *dragSource, const char *name )
+ : QStoredDrag( RECIPEITEMMIMETYPE, dragSource, name )
+{
+ if ( recipeItem ) {
+ QByteArray data;
+ QDataStream out( data, IO_WriteOnly );
+ out << recipeItem->recipeID();
+ out << recipeItem->title();
+ setEncodedData( data );
+ }
+}
+
+bool RecipeItemDrag::canDecode( QMimeSource* e )
+{
+ return e->provides( RECIPEITEMMIMETYPE );
+}
+
+bool RecipeItemDrag::decode( const QMimeSource* e, RecipeListItem& item )
+{
+ if ( !e )
+ return false;
+
+ QByteArray data = e->encodedData( RECIPEITEMMIMETYPE );
+ if ( data.isEmpty() )
+ return false;
+
+ QString title;
+ int recipeID;
+ QDataStream in( data, IO_ReadOnly );
+ in >> recipeID;
+ in >> title;
+
+ item.setTitle( title );
+ item.setRecipeID( recipeID );
+
+ return true;
+}
+
+class RecipeListToolTip : public QToolTip
+{
+public:
+ RecipeListToolTip( RecipeListView *view ) : QToolTip(view->viewport()), m_view(view)
+ {}
+
+ void maybeTip( const QPoint &point )
+ {
+ QListViewItem *item = m_view->itemAt( point );
+ if ( item ) {
+ QString text = m_view->tooltip(item,0);
+ if ( !text.isEmpty() )
+ tip( m_view->itemRect( item ), text );
+ }
+ }
+
+private:
+ RecipeListView *m_view;
+
+};
+
+
+RecipeListView::RecipeListView( QWidget *parent, RecipeDB *db ) : StdCategoryListView( parent, db ),
+ flat_list( false ),
+ m_uncat_item(0),
+ m_progress_dlg(0)
+{
+ setColumnText( 0, i18n( "Recipe" ) );
+
+ KConfig *config = KGlobal::config(); config->setGroup( "Performance" );
+ curr_limit = config->readNumEntry("CategoryLimit",-1);
+
+ KIconLoader il;
+ setPixmap( il.loadIcon( "categories", KIcon::NoGroup, 16 ) );
+
+ setSelectionMode( QListView::Extended );
+
+ (void)new RecipeListToolTip(this);
+}
+
+void RecipeListView::init()
+{
+ connect( database, SIGNAL( recipeCreated( const Element &, const ElementList & ) ), SLOT( createRecipe( const Element &, const ElementList & ) ) );
+ connect( database, SIGNAL( recipeRemoved( int ) ), SLOT( removeRecipe( int ) ) );
+ connect( database, SIGNAL( recipeRemoved( int, int ) ), SLOT( removeRecipe( int, int ) ) );
+ connect( database, SIGNAL( recipeModified( const Element &, const ElementList & ) ), SLOT( modifyRecipe( const Element &, const ElementList & ) ) );
+
+ StdCategoryListView::init();
+}
+
+QDragObject *RecipeListView::dragObject()
+{
+ RecipeListItem * item = dynamic_cast<RecipeListItem*>( currentItem() );
+ if ( item != 0 ) {
+ RecipeItemDrag * obj = new RecipeItemDrag( item, this, "Recipe drag item" );
+ /*const QPixmap *pm = item->pixmap(0);
+ if( pm )
+ obj->setPixmap( *pm );*/
+ return obj;
+ }
+ return 0;
+}
+
+bool RecipeListView::acceptDrag( QDropEvent *event ) const
+{
+ return RecipeItemDrag::canDecode( event );
+}
+
+QString RecipeListView::tooltip(QListViewItem *item, int /*column*/) const
+{
+ if ( item->rtti() == RECIPELISTITEM_RTTI ) {
+ RecipeListItem *recipe_it = (RecipeListItem*)item;
+
+ Recipe r;
+ database->loadRecipe(&r,RecipeDB::Meta|RecipeDB::Noatime,recipe_it->recipeID() );
+
+ KLocale *locale = KGlobal::locale();
+
+ return QString("<center><b>%7</b></center><center>__________</center>%1 %2<br />%3 %4<br />%5 %6")
+ .arg(i18n("Created:")).arg(locale->formatDateTime(r.ctime))
+ .arg(i18n("Modified:")).arg(locale->formatDateTime(r.mtime))
+ .arg(i18n("Last Accessed:")).arg(locale->formatDateTime(r.atime))
+ .arg(recipe_it->title());
+ }/* Maybe this would be handy
+ else if ( item->rtti() == CATEGORYLISTITEM_RTTI ) {
+ CategoryListItem *cat_it = (CategoryListItem*)item;
+
+ return QString("<b>%1</b><hr />%2: %3")
+ .arg(cat_it->categoryName())
+ .arg(i18n("Recipes"))
+ .arg(QString::number(WHATEVER THE CHILD COUNT IS));
+ }*/
+
+ return QString::null;
+}
+
+void RecipeListView::load(int limit, int offset)
+{
+ m_uncat_item = 0;
+
+ if ( flat_list ) {
+ ElementList recipeList;
+ database->loadRecipeList( &recipeList );
+
+ ElementList::const_iterator recipe_it;
+ for ( recipe_it = recipeList.begin();recipe_it != recipeList.end();++recipe_it ) {
+ Recipe recipe;
+ recipe.recipeID = ( *recipe_it ).id;
+ recipe.title = ( *recipe_it ).name;
+ createRecipe( recipe, -1 );
+ }
+ }
+ else {
+ StdCategoryListView::load(limit,offset);
+
+ if ( offset == 0 ) {
+ ElementList recipeList;
+ database->loadUncategorizedRecipes( &recipeList );
+
+ ElementList::const_iterator recipe_it;
+ for ( recipe_it = recipeList.begin();recipe_it != recipeList.end();++recipe_it ) {
+ Recipe recipe;
+ recipe.recipeID = ( *recipe_it ).id;
+ recipe.title = ( *recipe_it ).name;
+ createRecipe( recipe, -1 );
+ }
+ }
+ }
+}
+
+void RecipeListView::populate( QListViewItem *item )
+{
+ CategoryItemInfo *cat_item = dynamic_cast<CategoryItemInfo*>(item);
+ if ( !cat_item || cat_item->isPopulated() ) return;
+
+ delete item->firstChild(); //delete the "pseudo item"
+
+ if ( m_progress_dlg ){
+ m_progress_dlg->progressBar()->advance(1);
+ kapp->processEvents();
+ }
+
+ StdCategoryListView::populate(item);
+
+ if ( !flat_list ) {
+ int id = cat_item->categoryId();
+
+ // Now show the recipes
+ ElementList recipeList;
+ database->loadRecipeList( &recipeList, id );
+
+ ElementList::const_iterator recipe_it;
+ for ( recipe_it = recipeList.begin(); recipe_it != recipeList.end(); ++recipe_it ) {
+ Recipe recipe;
+ recipe.recipeID = ( *recipe_it ).id;
+ recipe.title = ( *recipe_it ).name;
+ createRecipe( recipe, id );
+ }
+ }
+}
+
+void RecipeListView::populateAll( QListViewItem *parent )
+{
+ bool first = false;
+ if ( !parent ) {
+ first = true;
+ m_progress_dlg = new KProgressDialog(this,"populate_all_prog_dlg",QString::null,i18n("Loading recipes"),true);
+ m_progress_dlg->setAllowCancel(false);
+ m_progress_dlg->progressBar()->setTotalSteps(0);
+ m_progress_dlg->progressBar()->setPercentageVisible(false);
+
+ m_progress_dlg->grabKeyboard(); //don't let the user keep hitting keys
+
+ parent = firstChild();
+ }
+ else {
+ populate( parent );
+ parent = parent->firstChild();
+ }
+
+ for ( QListViewItem *item = parent; item; item = item->nextSibling() ) {
+ if ( m_progress_dlg && m_progress_dlg->wasCancelled() )
+ break;
+
+ populateAll( item );
+ }
+
+ if ( first ) {
+ delete m_progress_dlg;
+ m_progress_dlg = 0;
+ }
+}
+
+void RecipeListView::createRecipe( const Recipe &recipe, int parent_id )
+{
+ if ( parent_id == -1 ) {
+ if ( !m_uncat_item && curr_offset == 0 ) {
+ m_uncat_item = new UncategorizedItem(this);
+ if ( childCount() == 1 ) //only call createElement if this is the only item in the list
+ createElement(m_uncat_item); //otherwise, this item won't stay at the top
+ }
+
+ if ( m_uncat_item )
+ new RecipeListItem( m_uncat_item, recipe );
+ }
+ else {
+ CategoryListItem *parent = (CategoryListItem*)items_map[ parent_id ];
+ if ( parent && parent->isPopulated() )
+ createElement(new RecipeListItem( parent, recipe ));
+ }
+}
+
+void RecipeListView::createRecipe( const Element &recipe_el, const ElementList &categories )
+{
+ Recipe recipe;
+ recipe.recipeID = recipe_el.id;
+ recipe.title = recipe_el.name;
+
+ if ( categories.count() == 0 ) {
+ createRecipe( recipe, -1 );
+ }
+ else {
+ for ( ElementList::const_iterator cat_it = categories.begin(); cat_it != categories.end(); ++cat_it ) {
+ int cur_cat_id = ( *cat_it ).id;
+
+ QListViewItemIterator iterator( this );
+ while ( iterator.current() ) {
+ if ( iterator.current() ->rtti() == 1001 ) {
+ CategoryListItem * cat_item = ( CategoryListItem* ) iterator.current();
+ if ( cat_item->categoryId() == cur_cat_id ) {
+ createRecipe( recipe, cur_cat_id );
+ }
+ }
+ ++iterator;
+ }
+ }
+ }
+}
+
+void RecipeListView::createElement( QListViewItem *item )
+{
+ CategoryItemInfo *cat_item = dynamic_cast<CategoryItemInfo*>(item);
+ if ( cat_item && !cat_item->isPopulated() ) {
+ new PseudoListItem( item );
+ }
+
+ //if ( cat_item && !cat_item->isPopulated() && item->rtti() == RECIPELISTITEM_RTTI )
+ // return;
+
+ #if 0
+ ElementList list;
+ database->loadRecipeList( &list, cat_item->categoryId() );
+ if ( list.count() > 0 )
+ #endif
+
+ CategoryListView::createElement(item);
+}
+
+void RecipeListView::modifyRecipe( const Element &recipe, const ElementList &categories )
+{
+ removeRecipe( recipe.id );
+ createRecipe( recipe, categories );
+}
+
+void RecipeListView::removeRecipe( int id )
+{
+ QListViewItemIterator iterator( this );
+ while ( iterator.current() ) {
+ if ( iterator.current() ->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current();
+ if ( recipe_it->recipeID() == id ) {
+ removeElement(recipe_it);
+
+ //delete the "Uncategorized" item if we removed the last recipe that was under it
+ if ( m_uncat_item && m_uncat_item->childCount() == 0 ) {
+ delete m_uncat_item;
+ m_uncat_item = 0;
+ }
+ }
+ }
+ ++iterator;
+ }
+}
+
+void RecipeListView::removeRecipe( int recipe_id, int cat_id )
+{
+ QListViewItem * item = items_map[ cat_id ];
+
+ //find out if this is the only category the recipe belongs to
+ int finds = 0;
+ QListViewItemIterator iterator( this );
+ while ( iterator.current() ) {
+ if ( iterator.current() ->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current();
+
+ if ( recipe_it->recipeID() == recipe_id ) {
+ if ( finds > 1 )
+ break;
+ finds++;
+ }
+ }
+ ++iterator;
+ }
+
+ //do this to only iterate over children of 'item'
+ QListViewItem *pEndItem = NULL;
+ QListViewItem *pStartItem = item;
+ do {
+ if ( pStartItem->nextSibling() )
+ pEndItem = pStartItem->nextSibling();
+ else
+ pStartItem = pStartItem->parent();
+ }
+ while ( pStartItem && !pEndItem );
+
+ iterator = QListViewItemIterator( item );
+ while ( iterator.current() != pEndItem ) {
+ if ( iterator.current() ->rtti() == 1000 ) {
+ RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current();
+
+ if ( recipe_it->recipeID() == recipe_id ) {
+
+ if ( finds == 1 ) {
+ //the item is now uncategorized
+ if ( !m_uncat_item && curr_offset == 0 )
+ m_uncat_item = new UncategorizedItem(this);
+ if ( m_uncat_item ) {
+ Recipe r;
+ r.title = recipe_it->title(); r.recipeID = recipe_id;
+ new RecipeListItem(m_uncat_item,r);
+ }
+ }
+ removeElement(recipe_it);
+ break;
+ }
+ }
+ ++iterator;
+ }
+}
+
+void RecipeListView::removeCategory( int id )
+{
+ QListViewItem * item = items_map[ id ];
+ if ( !item )
+ return ; //this may have been deleted already by its parent being deleted
+
+ moveChildrenToRoot( item );
+
+ StdCategoryListView::removeCategory( id );
+}
+
+void RecipeListView::moveChildrenToRoot( QListViewItem *item )
+{
+ QListViewItem * next_sibling;
+ for ( QListViewItem * it = item->firstChild(); it; it = next_sibling ) {
+ next_sibling = it->nextSibling();
+ if ( it->rtti() == 1000 ) {
+ RecipeListItem *recipe_it = (RecipeListItem*) it;
+ Recipe r;
+ r.title = recipe_it->title(); r.recipeID = recipe_it->recipeID();
+
+ //the item is now uncategorized
+ removeElement(it,false);
+ it->parent() ->takeItem( it );
+ if ( !m_uncat_item && curr_offset == 0 )
+ m_uncat_item = new UncategorizedItem(this);
+ if ( m_uncat_item )
+ new RecipeListItem(m_uncat_item,r);
+ }
+ moveChildrenToRoot( it );
+ delete it;
+ }
+}
+
+#include "recipelistview.moc"
diff --git a/krecipes/src/widgets/recipelistview.h b/krecipes/src/widgets/recipelistview.h
new file mode 100644
index 0000000..83061a3
--- /dev/null
+++ b/krecipes/src/widgets/recipelistview.h
@@ -0,0 +1,166 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef RECIPELISTVIEW_H
+#define RECIPELISTVIEW_H
+
+#include <qdragobject.h>
+
+#include "categorylistview.h"
+#include "datablocks/recipe.h"
+
+class QDragObject;
+class QDropEvent;
+
+class KProgressDialog;
+
+class RecipeDB;
+
+#define RECIPELISTITEM_RTTI 1000
+
+#define RECIPEITEMMIMETYPE "data/x-kde.recipe.item"
+
+class RecipeListItem : public QListViewItem
+{
+public:
+ RecipeListItem( QListView* qlv, const Recipe &r ) : QListViewItem( qlv )
+ {
+ init( r );
+ }
+
+ RecipeListItem( QListView* qlv, QListViewItem *after, const Recipe &r ) : QListViewItem( qlv, after )
+ {
+ init( r );
+ }
+
+ RecipeListItem( CategoryListItem* it, const Recipe &r ) : QListViewItem( it )
+ {
+ init( r );
+ }
+
+ RecipeListItem( CategoryListItem* it, QListViewItem *after, const Recipe &r ) : QListViewItem( it, after )
+ {
+ init( r );
+ }
+
+ RecipeListItem( QListViewItem* it, const Recipe &r ) : QListViewItem( it )
+ {
+ init( r );
+ }
+
+ int rtti() const
+ {
+ return RECIPELISTITEM_RTTI;
+ }
+
+ ~RecipeListItem( void )
+ {
+ delete recipeStored;
+ }
+
+ int recipeID() const
+ {
+ return recipeStored->recipeID;
+ }
+ QString title() const
+ {
+ return recipeStored->title;
+ }
+
+ void setRecipeID( int id )
+ {
+ recipeStored->recipeID = id;
+ }
+ void setTitle( const QString &title )
+ {
+ recipeStored->title = title;
+ }
+
+protected:
+ Recipe *recipeStored;
+
+public:
+ virtual QString text( int column ) const
+ {
+ switch ( column ) {
+ case 0:
+ return ( recipeStored->title );
+ break;
+ case 1:
+ return ( QString::number( recipeStored->recipeID ) );
+ break;
+ default:
+ return ( QString::null );
+ }
+ }
+
+private:
+ void init( const Recipe &r )
+ {
+ recipeStored = new Recipe();
+
+ //note: we only store the title and id
+ recipeStored->recipeID = r.recipeID;
+ recipeStored->title = r.title;
+ }
+};
+
+class RecipeItemDrag : public QStoredDrag
+{
+public:
+ RecipeItemDrag( RecipeListItem *recipeItem, QWidget *dragSource = 0, const char *name = 0 );
+ static bool canDecode( QMimeSource* e );
+ static bool decode( const QMimeSource* e, RecipeListItem& item );
+};
+
+class RecipeListView : public StdCategoryListView
+{
+ Q_OBJECT
+
+public:
+ RecipeListView( QWidget *parent, RecipeDB *db );
+
+public slots:
+ void populateAll( QListViewItem *parent = 0 );
+
+protected slots:
+ virtual void createRecipe( const Recipe &, int parent_id );
+ virtual void createRecipe( const Element &recipe, const ElementList &categories );
+ virtual void modifyRecipe( const Element &recipe, const ElementList &categories );
+ virtual void removeRecipe( int );
+ virtual void removeRecipe( int, int );
+
+protected:
+ virtual void init();
+ virtual void createElement( QListViewItem * );
+ virtual void removeCategory( int id );
+ virtual QDragObject *dragObject();
+ virtual bool acceptDrag( QDropEvent *event ) const;
+ virtual void populate( QListViewItem *item );
+ virtual QString tooltip(QListViewItem *item, int column) const;
+
+ friend class RecipeListToolTip;
+
+ void load(int limit, int offset);
+
+private:
+ void moveChildrenToRoot( QListViewItem * );
+
+ bool flat_list;
+ QListViewItem *m_uncat_item;
+ QListViewItem *lastElementCurrLevel;
+
+ KProgressDialog *m_progress_dlg;
+};
+
+#endif //RECIPELISTVIEW_H
diff --git a/krecipes/src/widgets/unitcombobox.cpp b/krecipes/src/widgets/unitcombobox.cpp
new file mode 100644
index 0000000..32766a0
--- /dev/null
+++ b/krecipes/src/widgets/unitcombobox.cpp
@@ -0,0 +1,145 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "unitcombobox.h"
+
+#include <qlistbox.h>
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kglobal.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/elementlist.h"
+
+UnitComboBox::UnitComboBox( QWidget *parent, RecipeDB *db, Unit::Type type ) : KComboBox( parent ),
+ database( db ), m_type(type)
+{
+ connect( database, SIGNAL( unitCreated( const Unit & ) ), SLOT( createUnit( const Unit & ) ) );
+ connect( database, SIGNAL( unitRemoved( int ) ), SLOT( removeUnit( int ) ) );
+}
+
+void UnitComboBox::popup()
+{
+ if ( count() == 1 )
+ reload();
+ KComboBox::popup();
+}
+
+Unit UnitComboBox::unit() const
+{
+ Unit u;
+ u.name = currentText();
+ u.id = id(currentItem());
+ return u;
+}
+
+void UnitComboBox::reload()
+{
+ QString remember_filter = currentText();
+
+ UnitList unitList;
+ database->loadUnits( &unitList, m_type );
+
+ clear();
+ unitComboRows.clear();
+
+ //Now load the categories
+ loadUnits(unitList);
+
+ if ( listBox()->findItem( remember_filter, Qt::ExactMatch ) ) {
+ setCurrentText( remember_filter );
+ }
+}
+
+void UnitComboBox::loadUnits( const UnitList &unitList )
+{
+ int row = 0;
+ for ( UnitList::const_iterator it = unitList.begin(); it != unitList.end(); ++it ) {
+ insertItem( (*it).name );
+ unitComboRows.insert( row, (*it).id ); // store unit id's in the combobox position to obtain the unit id later
+ row++;
+ }
+}
+
+void UnitComboBox::setSelected( int unitID )
+{
+ //do a reverse lookup on the row->id map
+ QMap<int, int>::const_iterator it;
+ for ( it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) {
+ if ( it.data() == unitID ) {
+ setCurrentItem(it.key());
+ break;
+ }
+ }
+}
+
+int UnitComboBox::id( int row ) const
+{
+ return unitComboRows[ row ];
+}
+
+void UnitComboBox::createUnit( const Unit &element )
+{
+ int row = findInsertionPoint( element.name );
+
+ insertItem( element.name, row );
+
+ //now update the map by pushing everything after this item down
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) {
+ if ( it.key() >= row ) {
+ new_map.insert( it.key() + 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ unitComboRows = new_map;
+ unitComboRows.insert( row, element.id );
+}
+
+void UnitComboBox::removeUnit( int id )
+{
+ int row = -1;
+ for ( QMap<int, int>::iterator it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) {
+ if ( it.data() == id ) {
+ row = it.key();
+ removeItem( row );
+ unitComboRows.remove( it );
+ break;
+ }
+ }
+
+ if ( row == -1 )
+ return ;
+
+ //now update the map by pushing everything after this item up
+ QMap<int, int> new_map;
+ for ( QMap<int, int>::iterator it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) {
+ if ( it.key() > row ) {
+ new_map.insert( it.key() - 1, it.data() );
+ }
+ else
+ new_map.insert( it.key(), it.data() );
+ }
+ unitComboRows = new_map;
+}
+
+int UnitComboBox::findInsertionPoint( const QString &name )
+{
+ for ( int i = 1; i < count(); i++ ) {
+ if ( QString::localeAwareCompare( name, text( i ) ) < 0 )
+ return i;
+ }
+
+ return count();
+}
+
+#include "unitcombobox.moc"
diff --git a/krecipes/src/widgets/unitcombobox.h b/krecipes/src/widgets/unitcombobox.h
new file mode 100644
index 0000000..9f21be3
--- /dev/null
+++ b/krecipes/src/widgets/unitcombobox.h
@@ -0,0 +1,52 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef UNITCOMBOBOX_H
+#define UNITCOMBOBOX_H
+
+#include <kcombobox.h>
+
+#include <qmap.h>
+
+#include "datablocks/unit.h"
+
+class RecipeDB;
+
+class UnitComboBox : public KComboBox
+{
+ Q_OBJECT
+
+public:
+ UnitComboBox( QWidget *parent, RecipeDB *db, Unit::Type type = Unit::All );
+
+ void reload();
+ int id( int row ) const;
+ void setSelected( int unitID );
+ Unit unit() const;
+
+protected:
+ virtual void popup();
+
+private slots:
+ void createUnit( const Unit & );
+ void removeUnit( int id );
+
+ int findInsertionPoint( const QString &name );
+
+private:
+ void loadUnits( const UnitList &unitList );
+
+ RecipeDB *database;
+ QMap<int, int> unitComboRows; // Contains the unit id for every given row in the unit combobox
+ Unit::Type m_type;
+};
+
+#endif //UNITCOMBOBOX_H
+
diff --git a/krecipes/src/widgets/unitlistview.cpp b/krecipes/src/widgets/unitlistview.cpp
new file mode 100644
index 0000000..1259d4a
--- /dev/null
+++ b/krecipes/src/widgets/unitlistview.cpp
@@ -0,0 +1,369 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "unitlistview.h"
+
+#include <qcombobox.h>
+#include <qheader.h>
+
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kdebug.h>
+
+#include "backends/recipedb.h"
+#include "dialogs/createunitdialog.h"
+#include "dialogs/dependanciesdialog.h"
+#include "datablocks/unit.h"
+
+class UnitListViewItem : public QListViewItem
+{
+public:
+ UnitListViewItem( QListView* qlv, const Unit &u ) : QListViewItem( qlv ), m_unit(u)
+ {
+ updateType(m_unit.type);
+ }
+
+ virtual QString text( int column ) const
+ {
+ switch ( column ) {
+ case 0: return m_unit.name;
+ case 1: return m_unit.name_abbrev;
+ case 2: return m_unit.plural;
+ case 3: return m_unit.plural_abbrev;
+ case 4: return m_type;
+ case 5: return QString::number(m_unit.id);
+ default: return QString::null;
+ }
+ }
+
+ void setType( Unit::Type type ){ m_unit.type = type; updateType(type); }
+
+ Unit unit() const { return m_unit; };
+ void setUnit( const Unit &u ) { m_unit = u; }
+
+protected:
+ virtual void setText( int column, const QString &text ) {
+ switch ( column ) {
+ case 0: m_unit.name = text; break;
+ case 1: m_unit.name_abbrev = text; break;
+ case 2: m_unit.plural = text; break;
+ case 3: m_unit.plural_abbrev = text; break;
+ }
+ }
+
+private:
+ void updateType( Unit::Type t ) {
+ switch ( t ) {
+ case Unit::Other: m_type = i18n("Other"); break;
+ case Unit::Mass: m_type = i18n("Mass"); break;
+ case Unit::Volume: m_type = i18n("Volume"); break;
+ default: break;
+ }
+ }
+
+ Unit m_unit;
+ QString m_type;
+};
+
+UnitListView::UnitListView( QWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db,db->unitCount() )
+{
+ setAllColumnsShowFocus( true );
+ setDefaultRenameAction( QListView::Reject );
+}
+
+void UnitListView::init()
+{
+ connect( database, SIGNAL( unitCreated( const Unit & ) ), SLOT( checkCreateUnit( const Unit & ) ) );
+ connect( database, SIGNAL( unitRemoved( int ) ), SLOT( removeUnit( int ) ) );
+}
+
+void UnitListView::load( int limit, int offset )
+{
+ UnitList unitList;
+ database->loadUnits( &unitList, Unit::All, limit, offset );
+
+ for ( UnitList::const_iterator it = unitList.begin(); it != unitList.end(); ++it ) {
+ if ( !( *it ).name.isEmpty() || !( *it ).plural.isEmpty() )
+ createUnit( *it );
+ }
+}
+
+void UnitListView::checkCreateUnit( const Unit &el )
+{
+ if ( handleElement(el.name) ) { //only create this unit if the base class okays it
+ createUnit(el);
+ }
+}
+
+
+StdUnitListView::StdUnitListView( QWidget *parent, RecipeDB *db, bool editable ) : UnitListView( parent, db )
+{
+ addColumn( i18n( "Unit" ) );
+ addColumn( i18n( "Abbreviation" ) );
+ addColumn( i18n( "Plural" ) );
+ addColumn( i18n( "Abbreviation" ) );
+ addColumn( i18n( "Type" ) );
+
+ KConfig * config = KGlobal::config();
+ config->setGroup( "Advanced" );
+ bool show_id = config->readBoolEntry( "ShowID", false );
+ addColumn( i18n( "Id" ), show_id ? -1 : 0 );
+
+ if ( editable ) {
+ setRenameable( 0, true );
+ setRenameable( 1, true );
+ setRenameable( 2, true );
+ setRenameable( 3, true );
+ setRenameable( 4, true );
+
+ KIconLoader il;
+
+ kpop = new KPopupMenu( this );
+ kpop->insertItem( il.loadIcon( "filenew", KIcon::NoGroup, 16 ), i18n( "&Create" ), this, SLOT( createNew() ), CTRL + Key_C );
+ kpop->insertItem( il.loadIcon( "editdelete", KIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove
+ () ), Key_Delete );
+ kpop->insertItem( il.loadIcon( "edit", KIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R );
+ kpop->polish();
+
+ typeComboBox = new QComboBox( false, viewport() );
+ typeComboBox->insertItem(i18n("Other"));
+ typeComboBox->insertItem(i18n("Mass"));
+ typeComboBox->insertItem(i18n("Volume"));
+ addChild( typeComboBox );
+ typeComboBox->hide();
+
+ connect( typeComboBox, SIGNAL( activated(int) ), SLOT( updateType(int) ) );
+ connect( this, SIGNAL( selectionChanged() ), SLOT( hideTypeCombo() ) );
+
+ connect( this, SIGNAL( contextMenu( KListView *, QListViewItem *, const QPoint & ) ), SLOT( showPopup( KListView *, QListViewItem *, const QPoint & ) ) );
+ connect( this, SIGNAL( doubleClicked( QListViewItem*, const QPoint &, int ) ), this, SLOT( modUnit( QListViewItem*, const QPoint &, int ) ) );
+ connect( this, SIGNAL( itemRenamed( QListViewItem*, const QString &, int ) ), this, SLOT( saveUnit( QListViewItem*, const QString &, int ) ) );
+ }
+}
+
+void StdUnitListView::insertTypeComboBox( QListViewItem* it )
+{
+ QRect r;
+
+ // Constraints Box1
+ r = header() ->sectionRect( 4 ); //start at the section 2 header
+ r.moveBy( 0, itemRect( it ).y() ); //Move down to the item, note that its height is same as header's right now.
+
+ r.setHeight( it->height() ); // Set the item's height
+ r.setWidth( header() ->sectionRect( 4 ).width() ); // and width
+ typeComboBox->setGeometry( r );
+
+ UnitListViewItem *unit_it = (UnitListViewItem*)it;
+ typeComboBox->setCurrentItem( unit_it->unit().type );
+
+ typeComboBox->show();
+}
+
+void StdUnitListView::updateType( int type )
+{
+ UnitListViewItem *unit_it = (UnitListViewItem*)currentItem();
+ unit_it->setType((Unit::Type)type);
+
+ database->modUnit( unit_it->unit() );
+}
+
+void StdUnitListView::hideTypeCombo()
+{
+ typeComboBox->hide();
+}
+
+void StdUnitListView::showPopup( KListView * /*l*/, QListViewItem *i, const QPoint &p )
+{
+ if ( i )
+ kpop->exec( p );
+}
+
+void StdUnitListView::createNew()
+{
+ CreateUnitDialog * unitDialog = new CreateUnitDialog( this );
+
+ if ( unitDialog->exec() == QDialog::Accepted ) {
+ Unit result = unitDialog->newUnit();
+
+ //check bounds first
+ if ( checkBounds( result )
+ && database->findExistingUnitByName( result.name ) == -1
+ && database->findExistingUnitByName( result.plural ) == -1
+ ) {
+ database->createNewUnit( result );
+ }
+ }
+ delete unitDialog;
+}
+
+void StdUnitListView::remove()
+{
+ // Find selected unit item
+ UnitListViewItem* it = (UnitListViewItem*)currentItem();
+
+ if ( it ) {
+ int unitID = it->unit().id;
+
+ ElementList recipeDependancies, propertyDependancies, weightDependancies;
+ database->findUnitDependancies( unitID, &propertyDependancies, &recipeDependancies, &weightDependancies );
+
+ QValueList<ListInfo> lists;
+ if ( !recipeDependancies.isEmpty() ) {
+ ListInfo info;
+ info.list = recipeDependancies;
+ info.name = i18n("Recipes");
+ lists << info;
+ }
+ if ( !propertyDependancies.isEmpty() ) {
+ ListInfo info;
+ info.list = propertyDependancies;
+ info.name = i18n("Properties");
+ lists << info;
+ }
+ if ( !weightDependancies.isEmpty() ) {
+ ListInfo info;
+ info.list = weightDependancies;
+ info.name = i18n("Ingredient Weights");
+ lists << info;
+ }
+
+ if ( lists.isEmpty() )
+ database->removeUnit( unitID );
+ else { // need warning!
+ DependanciesDialog warnDialog( this, lists );
+ if ( !recipeDependancies.isEmpty() )
+ warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") );
+ if ( warnDialog.exec() == QDialog::Accepted )
+ database->removeUnit( unitID );
+ }
+ }
+}
+
+void StdUnitListView::rename()
+{
+ QListViewItem * item = currentItem();
+
+ if ( item ) {
+ CreateUnitDialog unitDialog( this, item->text(0), item->text(2), item->text(1), item->text(3), false );
+
+ if ( unitDialog.exec() == QDialog::Accepted ) {
+ UnitListViewItem *unit_item = (UnitListViewItem*)item;
+ Unit origUnit = unit_item->unit();
+ Unit newUnit = unitDialog.newUnit();
+
+ //for each changed entry, save the change individually
+
+ Unit unit = origUnit;
+
+ if ( newUnit.name != origUnit.name ) {
+ unit.name = newUnit.name;
+ unit_item->setUnit( unit );
+ saveUnit( unit_item, newUnit.name, 0 );
+
+ //saveUnit will call database->modUnit which deletes the list item we were using
+ unit_item = (UnitListViewItem*) findItem( QString::number(unit.id), 5 );
+ }
+
+ if ( newUnit.plural != origUnit.plural ) {
+ unit.plural = newUnit.plural;
+ unit_item->setUnit( unit );
+ saveUnit( unit_item, newUnit.plural, 2 );
+ unit_item = (UnitListViewItem*) findItem( QString::number(unit.id), 5 );
+ }
+
+ if ( !newUnit.name_abbrev.stripWhiteSpace().isEmpty() && newUnit.name_abbrev != origUnit.name_abbrev ) {
+ unit.name_abbrev = newUnit.name_abbrev;
+ unit_item->setUnit( unit );
+ saveUnit( unit_item, newUnit.name_abbrev, 1 );
+ unit_item = (UnitListViewItem*) findItem( QString::number(unit.id), 5 );
+ }
+ if ( !newUnit.plural_abbrev.stripWhiteSpace().isEmpty() && newUnit.plural_abbrev != origUnit.plural_abbrev ) {
+ unit.plural_abbrev = newUnit.plural_abbrev;
+ unit_item->setUnit( unit );
+ saveUnit( unit_item, newUnit.plural_abbrev, 3 );
+ }
+ }
+ }
+}
+
+void StdUnitListView::createUnit( const Unit &unit )
+{
+ createElement(new UnitListViewItem( this, unit ));
+}
+
+void StdUnitListView::removeUnit( int id )
+{
+ QListViewItem * item = findItem( QString::number( id ), 5 );
+ removeElement(item);
+}
+
+void StdUnitListView::modUnit( QListViewItem* i, const QPoint & /*p*/, int c )
+{
+ if ( i ) {
+ if ( c != 4 )
+ UnitListView::rename( i, c );
+ else {
+ insertTypeComboBox(i);
+ }
+ }
+}
+
+void StdUnitListView::saveUnit( QListViewItem* i, const QString &text, int c )
+{
+ //skip abbreviations
+ if ( c == 0 || c == 2 ) {
+ if ( !checkBounds( Unit( text, text ) ) ) {
+ reload(ForceReload); //reset the changed text
+ return ;
+ }
+ }
+
+ int existing_id = database->findExistingUnitByName( text );
+
+ UnitListViewItem *unit_it = (UnitListViewItem*)i;
+ int unit_id = unit_it->unit().id;
+ if ( existing_id != -1 && existing_id != unit_id && !text.stripWhiteSpace().isEmpty() ) { //unit already exists with this label... merge the two
+ switch ( KMessageBox::warningContinueCancel( this, i18n( "This unit already exists. Continuing will merge these two units into one. Are you sure?" ) ) ) {
+ case KMessageBox::Continue: {
+ database->modUnit( unit_it->unit() );
+ database->mergeUnits( unit_id, existing_id );
+ break;
+ }
+ default:
+ reload(ForceReload);
+ break;
+ }
+ }
+ else {
+ database->modUnit( unit_it->unit() );
+ }
+}
+
+bool StdUnitListView::checkBounds( const Unit &unit )
+{
+ if ( unit.name.length() > uint(database->maxUnitNameLength()) || unit.plural.length() > uint(database->maxUnitNameLength()) ) {
+ KMessageBox::error( this, QString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) );
+ return false;
+ }
+ else if ( unit.name.stripWhiteSpace().isEmpty() || unit.plural.stripWhiteSpace().isEmpty() )
+ return false;
+
+ return true;
+}
+
+#include "unitlistview.moc"
diff --git a/krecipes/src/widgets/unitlistview.h b/krecipes/src/widgets/unitlistview.h
new file mode 100644
index 0000000..6833f5d
--- /dev/null
+++ b/krecipes/src/widgets/unitlistview.h
@@ -0,0 +1,76 @@
+/***************************************************************************
+* Copyright (C) 2004 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* Unai Garro (ugarro@users.sourceforge.net) *
+* Cyril Bosselut (bosselut@b1project.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef UNITLISTVIEW_H
+#define UNITLISTVIEW_H
+
+#include "dblistviewbase.h"
+
+#include "datablocks/unit.h"
+
+class QComboBox;
+
+class RecipeDB;
+class KPopupMenu;
+
+class UnitListView : public DBListViewBase
+{
+ Q_OBJECT
+
+public:
+ UnitListView( QWidget *parent, RecipeDB *db );
+
+public slots:
+ virtual void load( int curr_limit, int curr_offset );
+
+protected slots:
+ virtual void createUnit( const Unit & ) = 0;
+ virtual void removeUnit( int ) = 0;
+
+ void checkCreateUnit( const Unit &el );
+
+protected:
+ virtual void init();
+};
+
+class StdUnitListView : public UnitListView
+{
+ Q_OBJECT
+
+public:
+ StdUnitListView( QWidget *parent, RecipeDB *db, bool editable = false );
+
+protected:
+ virtual void createUnit( const Unit & );
+ virtual void removeUnit( int );
+
+private slots:
+ void showPopup( KListView *, QListViewItem *, const QPoint & );
+ void hideTypeCombo();
+ void updateType( int type );
+
+ void createNew();
+ void remove();
+ void rename();
+
+ void modUnit( QListViewItem* i, const QPoint &p, int c );
+ void saveUnit( QListViewItem* i, const QString &text, int c );
+
+private:
+ bool checkBounds( const Unit &unit );
+ void insertTypeComboBox( QListViewItem* );
+
+ KPopupMenu *kpop;
+ QComboBox *typeComboBox;
+};
+
+#endif //UNITLISTVIEW_H
diff --git a/krecipes/src/widgets/weightinput.cpp b/krecipes/src/widgets/weightinput.cpp
new file mode 100644
index 0000000..2bf2d2c
--- /dev/null
+++ b/krecipes/src/widgets/weightinput.cpp
@@ -0,0 +1,63 @@
+/***************************************************************************
+* Copyright (C) 2006 by *
+* Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#include "weightinput.h"
+
+#include <klocale.h>
+
+#include "backends/recipedb.h"
+#include "datablocks/element.h"
+#include "datablocks/weight.h"
+#include "prepmethodcombobox.h"
+
+WeightInput::WeightInput( QWidget *parent, RecipeDB *database, Unit::Type type, MixedNumber::Format format ) :
+ AmountUnitInput(parent,database,type,format),
+ m_database(database)
+{
+ prepMethodBox = new PrepMethodComboBox(false,this,database,i18n("-No Preparation-"));
+ prepMethodBox->reload();
+
+ connect( prepMethodBox, SIGNAL(activated(int)), SLOT(emitValueChanged()) );
+}
+
+void WeightInput::emitValueChanged()
+{
+ Weight w;
+ w.perAmount = amount().toDouble();
+
+ Unit u = unit();
+ w.perAmountUnitID = u.id;
+ w.perAmountUnit = (w.perAmount>1)?u.plural:u.name;
+
+ Element prep;
+ w.prepMethod = prep.name;
+ w.prepMethodID = prep.id;
+ emit valueChanged( w );
+}
+
+void WeightInput::setPrepMethod( const Element &prep )
+{
+ if ( prep.id == -1 )
+ prepMethodBox->setCurrentItem(0);
+ else
+ prepMethodBox->setSelected( prep.id );
+
+}
+
+Element WeightInput::prepMethod() const
+{
+ Element prep;
+ prep.id = prepMethodBox->id( prepMethodBox->currentItem() );
+ if ( prep.id != -1 )
+ prep.name = prepMethodBox->currentText();
+ return prep;
+}
+
+#include "weightinput.moc"
diff --git a/krecipes/src/widgets/weightinput.h b/krecipes/src/widgets/weightinput.h
new file mode 100644
index 0000000..088bf26
--- /dev/null
+++ b/krecipes/src/widgets/weightinput.h
@@ -0,0 +1,42 @@
+/***************************************************************************
+* Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com) *
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+***************************************************************************/
+
+#ifndef WEIGHTINPUT_H
+#define WEIGHTINPUT_H
+
+#include "widgets/amountunitinput.h"
+#include "datablocks/unit.h"
+
+class RecipeDB;
+class PrepMethodComboBox;
+class Element;
+class Weight;
+
+class WeightInput : public AmountUnitInput
+{
+Q_OBJECT
+
+public:
+ WeightInput( QWidget *parent, RecipeDB *database, Unit::Type type = Unit::All, MixedNumber::Format f = MixedNumber::MixedNumberFormat );
+
+ Element prepMethod() const;
+ void setPrepMethod( const Element & );
+
+public slots:
+ void emitValueChanged();
+
+signals:
+ void valueChanged( const Weight & );
+
+private:
+ PrepMethodComboBox *prepMethodBox;
+
+ RecipeDB *m_database;
+};
+#endif